按比例控制流量的一种实现

网关做灰度的时候,要控制流量的比例,比如 3:7 的分发流量到两个不同版本的服务上去。刚开始的想法是每次流量过来生成 100 以内的随机数,随机数落在那个区间就转到那个版本的服务上去,但是发现这样无法较精准的保证 3:7 的比例,因为有可能某段时间内生成的随机数大范围的落在某个区间内,比如请求了 100 次,每次生成的随机数都是大于 30 的,这样 70% 比例的服务就承受了 100% 的流量。

接下来想到了第二种解决方案,能够保证 10(基数) 倍的流量比例正好是 3:7,思路如下:

1、生成 0 - 99 的数组(集合)
2、打乱数组(集合)的顺序,为了防止出现某比例的流量集中出现
3、全局的计数器,要考虑原子性
4、从数组(集合)中取出计数器和 100 取余后位置的值
5、判断取到的值落在那个区间

以下是 Java 的简单实现:

1
package io.github.ehlxr.rate;
2
3
import java.util.Collections;
4
import java.util.List;
5
import java.util.concurrent.atomic.AtomicInteger;
6
7
/**
8
 * 按比例控制流量
9
 *
10
 * @author ehlxr
11
 * @since 2019-07-19.
12
 */
13
public class RateBarrier {
14
    private AtomicInteger op = new AtomicInteger(0);
15
    private List<Integer> source;
16
    private int base;
17
    private int rate;
18
19
    public boolean allow() {
20
        return source.get(op.incrementAndGet() % base) < rate;
21
    }
22
23
    private RateBarrier() {
24
    }
25
26
    public RateBarrier(int base, int rate) {
27
        this.base = base;
28
        this.rate = rate;
29
30
        source = new ArrayList<>(base);
31
        for (int i = 0; i < base; i++) {
32
            source.add(i);
33
        }
34
35
        // 打乱集合顺序
36
        Collections.shuffle(source);
37
    }
38
39
}

以下是 3:7 流量控制的测试:

1
package io.github.ehlxr.rate;
2
3
/**
4
 * @author ehlxr
5
 * @since 2019-07-19.
6
 */
7
public class Main {
8
    public static void main(String[] args) {
9
        RateBarrier rateBarrier = new RateBarrier(10, 3);
10
11
        final Thread[] threads = new Thread[20];
12
        for (int i = 0; i < threads.length; i++) {
13
            threads[i] = new Thread(() -> {
14
                if (rateBarrier.allow()) {
15
                    System.out.println("this is on 3");
16
                } else {
17
                    System.out.println("this is on 7");
18
                }
19
            });
20
            threads[i].start();
21
        }
22
23
        for (Thread t : threads) {
24
            try {
25
                t.join();
26
            } catch (InterruptedException e) {
27
                e.printStackTrace();
28
            }
29
        }
30
31
    }
32
}
33
34
// Output:
35
36
/*
37
this is on 7
38
this is on 7
39
this is on 7
40
this is on 7
41
this is on 3
42
this is on 7
43
this is on 3
44
this is on 7
45
this is on 7
46
this is on 3
47
this is on 7
48
this is on 7
49
this is on 7
50
this is on 7
51
this is on 3
52
this is on 7
53
this is on 3
54
this is on 7
55
this is on 7
56
this is on 3
57
*/

以下是 Golang 版本 2:3:5 比例分流的简单实现:

1
package main
2
3
import (
4
	"fmt"
5
	"math/rand"
6
	"sync"
7
	"sync/atomic"
8
)
9
10
type RateBarrier struct {
11
	source []int
12
	op     uint64
13
	base   int
14
}
15
16
func NewRateBarrier(base int) *RateBarrier {
17
	source := make([]int, base, base)
18
	for i := 0; i < base; i++ {
19
		source[i] = i
20
	}
21
22
	// 随机排序
23
	rand.Shuffle(base, func(i, j int) {
24
		source[i], source[j] = source[j], source[i]
25
	})
26
27
	return &RateBarrier{
28
		source: source,
29
		base:   base,
30
	}
31
}
32
33
func (b *RateBarrier) Rate() int {
34
	return b.source[int(atomic.AddUint64(&b.op, 1))%b.base]
35
}
36
37
func main() {
38
	var wg sync.WaitGroup
39
	wg.Add(20)
40
41
	// 2:3:5
42
	b := NewRateBarrier(10)
43
	for i := 0; i < 20; i++ {
44
		go func() {
45
			rate := b.Rate()
46
			switch {
47
			case rate < 2:
48
				fmt.Println("this is on 20%")
49
			case rate >= 2 && rate < 5:
50
				fmt.Println("this is on 30%")
51
			case rate >= 5:
52
				fmt.Println("this is on 50%")
53
			}
54
55
			wg.Done()
56
		}()
57
	}
58
59
	wg.Wait()
60
}
61
62
// Output:
63
/*
64
this is on 30%
65
this is on 50%
66
this is on 30%
67
this is on 20%
68
this is on 50%
69
this is on 50%
70
this is on 50%
71
this is on 20%
72
this is on 30%
73
this is on 20%
74
this is on 50%
75
this is on 30%
76
this is on 30%
77
this is on 50%
78
this is on 50%
79
this is on 50%
80
this is on 20%
81
this is on 50%
82
this is on 50%
83
this is on 30%
84
*/
欣赏此文?求鼓励,求支持!
Title - Artist
0:00