Dubbo源码分析——负载均衡

Dubbo源码分析——负载均衡

简介

目前dubbo中支持了四种负载均衡,分别是:最小活跃(LeastActive)、随机(Random)、加权平均(RoundRobin)和一致性哈希(ConsistentHash)。废话不多说直接上源码。

Random

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
int length = invokers.size();
int totalWeight = 0;
// 总共的权重
boolean sameWeight = true;
for (int i = 0; i < length; i++) {
int weight = getWeight(invokers.get(i), invocation);
// 累加每个invoker的权重
totalWeight += weight;
if (sameWeight && i > 0
&& weight != getWeight(invokers.get(i - 1), invocation)) {
// 如果不是所有invoker权重都一样,这个标志位为false
sameWeight = false;
}
}
// 如果不是所有invoker权重都一样,就根据权重去选取一个
if (totalWeight > 0 && !sameWeight) {
int offset = random.nextInt(totalWeight);
for (int i = 0; i < length; i++) {
offset -= getWeight(invokers.get(i), invocation);
if (offset < 0) {
return invokers.get(i);
}
}
}

// 如果权重都一样,就随机选一个
return invokers.get(random.nextInt(length));

这个还是比较简单的,终极思想就是——如果权重都一样就随机选一个,只要有一个权重和其他不一样的,就按照权重去选(权重大的会有更大几率被选中)。

有一个小点需要关注一下,就是权重是经过warmup的权重,warmup没结束的invoker,被选择的概率相对会第一些。也就是说,可能我们把一个invoker的权重设置的非常大,但是如果预热时间设置的非常久,那么计算出来的权重可能是非常低的,但是预热一过,权重就恢复到我们设置的值了。

Random也是默认的负载均衡策略。

LeastActive

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
int length = invokers.size();
int leastActive = -1;
int leastCount = 0;
int[] leastIndexs = new int[length];
// 经过warmup之后的总权重
int taotalWeightWithWarmUp = 0;
int firstWeightWithWarmUp = 0;
boolean sameWeight = true;
for (int i = 0; i < length; i++) {
Invoker<T> invoker = invokers.get(i);
// 目前active的请求数量
int active = RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName()).getActive();
// 某一个invoker的权重(warmup之后)
int afterWarmup = getWeight(invoker, invocation);
if (leastActive == -1 || active < leastActive) {
// 如果最小的活跃数小于leastActive,就把当前的invoker设置为最小活跃的那个
leastActive = active;
leastCount = 1;
leastIndexs[0] = i;
taotalWeightWithWarmUp = afterWarmup;
firstWeightWithWarmUp = afterWarmup;
sameWeight = true;
} else if (active == leastActive) {
// 如果活跃数相同,即同样是最小活跃的那个,就把自己在数组中保存起来
leastIndexs[leastCount++] = i;
// 累加权重
taotalWeightWithWarmUp += afterWarmup;
if (sameWeight && i > 0
&& afterWarmup != firstWeightWithWarmUp) {
// 如果最小活跃的这些invoker权重不一样,sameWeight置为false
sameWeight = false;
}
}
}

// 如果最小活跃的就一个,直接就用那个
if (leastCount == 1) {
return invokers.get(leastIndexs[0]);
}
// 如果最小活跃的不止一个,那么根据权重随机选取(类似random)
if (!sameWeight && taotalWeightWithWarmUp > 0) {
int offsetWeight = random.nextInt(taotalWeightWithWarmUp) + 1;
for (int i = 0; i < leastCount; i++) {
int leastIndex = leastIndexs[i];
offsetWeight -= getWeight(invokers.get(leastIndex), invocation);
if (offsetWeight <= 0)
return invokers.get(leastIndex);
}
}
// 如果最小活跃的invoker权重都一样,随机选一个
return invokers.get(leastIndexs[random.nextInt(leastCount)]);

其实也比较简单,就是先选那些最小活跃的(也可以认为是最空闲的),然后看看这些最小活跃的invoker权重一不一样,有一个不一样就按照权重选,否则就随机选。

RoundRobin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// 负载均衡算法细化到每个方法的调用
String key = invokers.get(0).getUrl().getServiceKey() + "." + invocation.getMethodName();

int length = invokers.size();
// 最大权重
int maxWeight = 0;
// 最小权重
int minWeight = Integer.MAX_VALUE;
final LinkedHashMap<Invoker<T>, IntegerWrapper> invokerToWeightMap = new LinkedHashMap<Invoker<T>, IntegerWrapper>();
int weightSum = 0;
for (int i = 0; i < length; i++) {
int weight = getWeight(invokers.get(i), invocation);
maxWeight = Math.max(maxWeight, weight);
minWeight = Math.min(minWeight, weight);
// 只有大于0的权重才参与加权
if (weight > 0) {
invokerToWeightMap.put(invokers.get(i), new IntegerWrapper(weight));
weightSum += weight;
}
}
AtomicPositiveInteger sequence = sequences.get(key);
if (sequence == null) {
sequences.putIfAbsent(key, new AtomicPositiveInteger());
sequence = sequences.get(key);
}
// 当前sequence
int currentSequence = sequence.getAndIncrement();
if (maxWeight > 0 && minWeight < maxWeight) {
int mod = currentSequence % weightSum;
for (int i = 0; i < maxWeight; i++) {
for (Map.Entry<Invoker<T>, IntegerWrapper> each : invokerToWeightMap.entrySet()) {
final Invoker<T> k = each.getKey();
// 可以认为v就是个int,只是包了一下
final IntegerWrapper v = each.getValue();
if (mod == 0 && v.getValue() > 0) {
return k;
}
if (v.getValue() > 0) {
v.decrement();
mod--;
}
}
}
}
return invokers.get(currentSequence % length);

这里其实用的是平滑按权轮训算法,这个地方就不细说了,大家可以搜搜加权轮训算法。

这里的好处是,非常平滑,如果两个invoker的权重是1:2:5,不会造成前五个请求都在权重为5的invoker上。

这个要读者自己去debug才能明白其中的精髓了。

ConsistentHash

一致性hash算法,同样不展开细说了,大家可以看看网上关于一致性hash的介绍。后面我也会细说这个算法。

这里列出来是需要注意一个点就是,dubbo的ConsistentHashLoadBalance与invoker的权重无关,即不管怎么设置invoker的权重,都对轮训的结果没有任何影响。