本文基于dubbo v2.6.x
1. 最小活跃数介绍
本文主要是讲解dubbo负载均衡的最少活跃数(并发数)算法,在阅读本文之前建议读者学习下这篇文章《深度解析dubbo过滤器之ActiveLimitFilter》了解这个活跃数是怎么来的,当然不了解这个活跃数是怎样算出来的可以,但是需要知道这个活跃数其实就是在当前这个服务调用者中当前这个时刻 某个invoker(某个服务提供者的某个接口)某个方法的调用并发数,在调用之前+1 调用之后-1的一个计数器。其实最少活跃数(并发数)算法字面上来说就是获取那个活跃数最小的那个invoker,遍历那一堆invokers,然后找出一个活跃数最小的invoker,这里dubbo提供最少活跃数LeastActiveLoadBalance 算法比这个复杂一些,它处理活跃数相等的情况,然后在最后如果出现多个活跃数相等invoker的时候使用随机算法来选取一个。接下来我们来具体看下dubbo最小活跃数的源码实现。
2. LeastActiveLoadBalance源码解析
我们先来看下LeastActiveLoadBalance 的class定义:
public class LeastActiveLoadBalance extends AbstractLoadBalance {
可以看到是继承AbstractLoadBalance这个抽象类的,在AbstractLoadBalance的select方法中帮我们处理了invokers==null 与invoker.size()==1的情况,那种invokers>1的情况就需要子类的doSelect 方法来实现了,我们接下来看下LeastActiveLoadBalance 的doSelect方法源码。
@Override
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
// 服务提供者列表的长度
int length = invokers.size(); // Number of invokers
// 最活跃初始值是-1
int leastActive = -1; // The least active value of all invokers
//具有相同的最小活动值的调用程序的数量(leastActive)
int leastCount = 0; // The number of invokers having the same least active value (leastActive)
int[] leastIndexs = new int[length]; // The index of invokers having the same least active value (leastActive)
// 权重和
int totalWeight = 0; // The sum of with warmup weights
//初始值 用于比较
int firstWeight = 0; // Initial value, used for comparision
// 每个invoker是否是相同的权重?
boolean sameWeight = true; // Every invoker has the same weight value?
for (int i = 0; i < length; i++) {
Invoker<T> invoker = invokers.get(i);
// 获取当前这个invoker并发数
int active = RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName()).getActive(); // Active number
// 计算权重值
int afterWarmup = getWeight(invoker, invocation); // Weight
// 第一个元素的后 或者 当前invoker并发数 小于 最小并发数(初始值是-1)
if (leastActive == -1 || active < leastActive) { // Restart, when find a invoker having smaller least active value.
// 记录leastActive 为当前的活跃数
leastActive = active; // Record the current least active value
//重置最小计数,基于当前最小计数重新计数
leastCount = 1; // Reset leastCount, count again based on current leastCount
//在0下标出放入这个索引
leastIndexs[0] = i; // Reset
// 总权重就是 当前invoker的权重
totalWeight = afterWarmup; // Reset
//第一个权重
firstWeight = afterWarmup; // Record the weight the first invoker
sameWeight = true; // Reset, every invoker has the same weight value?
} else if (active == leastActive) {
// 当前invoker的活跃数 与 leastActive相等
// If current invoker's active value equals with leaseActive, then accumulating.
// 记录索引位置,具有相同最小活跃数的计数器 +1
leastIndexs[leastCount++] = i; // Record index number of this invoker
//总权重 = 总权重+当前权重
totalWeight += afterWarmup; // Add this invoker's weight to totalWeight.
// If every invoker has the same weight?
if (sameWeight && i > 0
&& afterWarmup != firstWeight) {
sameWeight = false;
}
}
}
// assert(leastCount > 0)
if (leastCount == 1) {//如果我们恰好有一个调用程序具有最少的活动值,那么直接返回这个调用程序。
// If we got exactly one invoker having the least active value, return this invoker directly.
return invokers.get(leastIndexs[0]);
}
// -----------------------------------------------------------------------------------------------------------
// 如果每个invoker有不同的权重 && totalWeight > 0
if (!sameWeight && totalWeight > 0) {
// If (not every invoker has the same weight & at least one invoker's weight>0), select randomly based on totalWeight.
// 在totalWeight 范围内随机一个值
int offsetWeight = random.nextInt(totalWeight) + 1;
// Return a invoker based on the random value.
for (int i = 0; i < leastCount; i++) {
// 获取i位置的那个最小活跃 在invokers 里面的位置信息
int leastIndex = leastIndexs[i];
//offsetWeight - leastIndex 位置invoker的权重
offsetWeight -= getWeight(invokers.get(leastIndex), invocation);
// offsetWeight 小于0的话
if (offsetWeight <= 0)
// 返回这个位置的这个
return invokers.get(leastIndex);
}
}
// 具有相同权重或者是 总权重=0 的话就均匀返回
// If all invokers have the same weight value or totalWeight=0, return evenly.
return invokers.get(leastIndexs[random.nextInt(leastCount)]);
}
为了方便阅读,我这里将doSelect方法使用“------”分成了两部分,先说下这两部分分别干了啥:
第一部分:主要是找出 最小活跃数的invoker们,将他们在invokers集合的index记录到数组中,这里为什么说们呢,就是最小活跃数可能多个相同的。
第二部分:主要是出现了多个最小活跃数一样情况,该怎样处理,出现一个最小活跃数的invoker好说,直接返回那一个就可以了,多个的时候就需要使用随机算法来从那多个中选取一个。
我们先来看第一部分的代码内容:
先是获取invokers有多少个,定义了leastActive=-1 这个变量是存储最小的那个活跃数的,leastCount=0 这个一个最小活跃数个数的计数器,创建了记录最小活跃数invoker索引的数组,再就是权重和totalWeight 用来累加最小活跃数的权重的(这个主要是用于出现多个最小活跃情况的时候用来进行随机的),firstWeight用来记录最小活跃数第一个权重的,再就是sameWeight 来记录最小活跃数的权重值都相等,如果是都相等的话,到最后就没法使用权重来随机了,只能用索引随机的方式了。接着就是 for循环了,获取当前invoker,它的当前活跃值(并发数),权重值。
如果leastActive == -1 (表示这是第一次循环) 或者是 当前invoker活跃数比活跃数最小记录(leastActive)还小(这个表示出现更小活跃数的时候)
进入这个代码块中,将当前的活跃数赋值给活跃数最小记录(leastActive),最小活跃数的个数设置成1(leastCount),这个invoker的下标index被放入数组0位置。totalWeight=当前权重,firstWeight=当前权重,sameWeight = true,首次出现最小的活跃数,前面这些都是在做重置工作。
如果当前的活跃数 = 最小活跃数(也就是出现了两个最小活跃数相同的情况,这个出现是在第二次循环往后),将invoker在集合中的位置记录到数组中(在数组位置就是leastCount ),然后leastCount +1(最小活跃数的invoker个数+1),总权重加上当前invoker权重,如果sameWeight ==true 并且 第一个最小活跃数invoker的权重与当前invoker权重不相等的时候,sameWeight 设置成false,这个sameWeight 表示的是 最小活跃数invoker们的权重是否都相等,默认是相等的,然后出现不相等的时候设置成false。
接着循环完成后,如果这个leastCount ==1 ,表示就一个最小的,然后从放索引那个数组中取出第一个索引,从invokers获取这个索引位置的invoker返回就可以了。
if (leastCount == 1) {//如果我们恰好有一个调用程序具有最少的活动值,那么直接返回这个调用程序。
// If we got exactly one invoker having the least active value, return this invoker directly.
return invokers.get(leastIndexs[0]);
}
我们再来看下第二部分的代码内容:
(这部分内容可以参考一下dubbo 随机算法的实现:《深度解析dubbo负载均衡之RandomLoadBalance》)
当出现多个最小活跃数invoker的时候,判断 他们的权重是否都相等,如果有不相等的话,从总权重范围中随机出来一个offsetWeight,遍历那几个最小活跃数,使用offsetWeight 减去 invoker的权重,如果offsetWeight 变成了负的或者是0的话,就返回这个invoker。
如果那几个最小活跃数的invoker权重都相等的话,就是随机那几个invoker。