专门做素菜的网站,制作网站的布局网络,东莞网站程序,小红书代运营在上一课时我们了解了 LoadBalance 接口定义以及 AbstractLoadBalance 抽象类的内容#xff0c;还详细介绍了 ConsistentHashLoadBalance 以及 RandomLoadBalance 这两个实现类的核心原理和大致实现。本课时我们将继续介绍 LoadBalance 的剩余三个实现。
LeastActiveLoadBala…在上一课时我们了解了 LoadBalance 接口定义以及 AbstractLoadBalance 抽象类的内容还详细介绍了 ConsistentHashLoadBalance 以及 RandomLoadBalance 这两个实现类的核心原理和大致实现。本课时我们将继续介绍 LoadBalance 的剩余三个实现。
LeastActiveLoadBalance 最小活跃数
LeastActiveLoadBalance 使用的是最小活跃数负载均衡算法。它认为当前活跃请求数越小的 Provider 节点剩余的处理能力越多处理请求的效率也就越高那么该 Provider 在单位时间内就可以处理更多的请求所以我们应该优先将请求分配给该 Provider 节点。
LeastActiveLoadBalance 需要配合 ActiveLimitFilter 使用ActiveLimitFilter 会记录每个接口方法的活跃请求数在 LeastActiveLoadBalance 进行负载均衡时只会从活跃请求数最少的 Invoker 集合里挑选 Invoker。
在 LeastActiveLoadBalance 的实现中首先会选出所有活跃请求数最小的 Invoker 对象之后的逻辑与 RandomLoadBalance 完全一样即按照这些 Invoker 对象的权重挑选最终的 Invoker 对象。下面是 LeastActiveLoadBalance.doSelect() 方法的具体实现
protected T InvokerT doSelect(ListInvokerT invokers, URL url, Invocation invocation) {// 初始化Invoker数量int length invokers.size();// 记录最小的活跃请求数int leastActive -1;// 记录活跃请求数最小的Invoker集合的个数int leastCount 0;// 记录活跃请求数最小的Invoker在invokers数组中的下标位置 int[] leastIndexes new int[length];// 记录活跃请求数最小的Invoker集合中每个Invoker的权重值int[] weights new int[length];// 记录活跃请求数最小的Invoker集合中所有Invoker的权重值之和int totalWeight 0;// 记录活跃请求数最小的Invoker集合中第一个Invoker的权重值int firstWeight 0;// 活跃请求数最小的集合中所有Invoker的权重值是否相同boolean sameWeight true;for (int i 0; i length; i) { // 遍历所有Invoker获取活跃请求数最小的Invoker集合InvokerT invoker invokers.get(i);// 获取该Invoker的活跃请求数int active RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName()).getActive();// 获取该Invoker的权重int afterWarmup getWeight(invoker, invocation);weights[i] afterWarmup;// 比较活跃请求数if (leastActive -1 || active leastActive) {// 当前的Invoker是第一个活跃请求数最小的Invoker则记录如下信息leastActive active; // 重新记录最小的活跃请求数leastCount 1; // 重新记录活跃请求数最小的Invoker集合个数leastIndexes[0] i; // 重新记录InvokertotalWeight afterWarmup; // 重新记录总权重值firstWeight afterWarmup; // 该Invoker作为第一个Invoker记录其权重值sameWeight true; // 重新记录是否权重值相等} else if (active leastActive) { // 当前Invoker属于活跃请求数最小的Invoker集合leastIndexes[leastCount] i; // 记录该Invoker的下标totalWeight afterWarmup; // 更新总权重if (sameWeight afterWarmup ! firstWeight) {sameWeight false; // 更新权重值是否相等}}}// 如果只有一个活跃请求数最小的Invoker对象直接返回即可if (leastCount 1) {return invokers.get(leastIndexes[0]);}// 下面按照RandomLoadBalance的逻辑从活跃请求数最小的Invoker集合中随机选择一个Invoker对象返回if (!sameWeight totalWeight 0) {int offsetWeight ThreadLocalRandom.current().nextInt(totalWeight);for (int i 0; i leastCount; i) {int leastIndex leastIndexes[i];offsetWeight - weights[leastIndex];if (offsetWeight 0) {return invokers.get(leastIndex);}}}return invokers.get(leastIndexes[ThreadLocalRandom.current().nextInt(leastCount)]);
}ActiveLimitFilter 以及底层的 RpcStatus 记录活跃请求数的具体原理在前面的[第 30 课时]中我们已经详细分析过了这里不再重复如果有不清楚的地方你可以回顾之前课时相关的内容。
RoundRobinLoadBalance 加权轮询
RoundRobinLoadBalance 实现的是加权轮询负载均衡算法。
轮询指的是将请求轮流分配给每个 Provider。例如有 A、B、C 三个 Provider 节点按照普通轮询的方式我们会将第一个请求分配给 Provider A将第二个请求分配给 Provider B第三个请求分配给 Provider C第四个请求再次分配给 Provider A……如此循环往复。
轮询是一种无状态负载均衡算法实现简单适用于集群中所有 Provider 节点性能相近的场景。 但现实情况中就很难保证这一点了因为很容易出现集群中性能最好和最差的 Provider 节点处理同样流量的情况这就可能导致性能差的 Provider 节点各方面资源非常紧张甚至无法及时响应了但是性能好的 Provider 节点的各方面资源使用还较为空闲。这时我们可以通过加权轮询的方式降低分配到性能较差的 Provider 节点的流量。
加权之后分配给每个 Provider 节点的流量比会接近或等于它们的权重比。例如Provider 节点 A、B、C 权重比为 5:1:1那么在 7 次请求中节点 A 将收到 5 次请求节点 B 会收到 1 次请求节点 C 则会收到 1 次请求。
在 Dubbo 2.6.4 版本及之前RoundRobinLoadBalance 的实现存在一些问题例如选择 Invoker 的性能问题、负载均衡时不够平滑等。在 Dubbo 2.6.5 版本之后这些问题都得到了修复所以这里我们就来介绍最新的 RoundRobinLoadBalance 实现。
每个 Provider 节点有两个权重一个权重是配置的 weight该值在负载均衡的过程中不会变化另一个权重是 currentWeight该值会在负载均衡的过程中动态调整初始值为 0。
当有新的请求进来时
RoundRobinLoadBalance 会遍历 Invoker 列表并用对应的 currentWeight 加上其配置的权重。遍历完成后再找到最大的 currentWeight将其减去权重总和然后返回相应的 Invoker 对象。
下面我们通过一个示例说明 RoundRobinLoadBalance 的执行流程这里我们依旧假设 A、B、C 三个节点的权重比例为 5:1:1。 处理第一个请求currentWeight 数组中的权重与配置的 weight 相加即从 [0, 0, 0] 变为 [5, 1, 1]。接下来从中选择权重最大的 Invoker 作为结果即节点 A。最后将节点 A 的 currentWeight 值减去 totalWeight 值最终得到 currentWeight 数组为 [-2, 1, 1]。处理第二个请求currentWeight 数组中的权重与配置的 weight 相加即从 [-2, 1, 1] 变为 [3, 2, 2]。接下来从中选择权重最大的 Invoker 作为结果即节点 A。最后将节点 A 的 currentWeight 值减去 totalWeight 值最终得到 currentWeight 数组为 [-4, 2, 2]。处理第三个请求currentWeight 数组中的权重与配置的 weight 相加即从 [-4, 2, 2] 变为 [1, 3, 3]。接下来从中选择权重最大的 Invoker 作为结果即节点 B。最后将节点 B 的 currentWeight 值减去 totalWeight 值最终得到 currentWeight 数组为 [1, -4, 3]。处理第四个请求currentWeight 数组中的权重与配置的 weight 相加即从 [1, -4, 3] 变为 [6, -3, 4]。接下来从中选择权重最大的 Invoker 作为结果即节点 A。最后将节点 A 的 currentWeight 值减去 totalWeight 值最终得到 currentWeight 数组为 [-1, -3, 4]。处理第五个请求currentWeight 数组中的权重与配置的 weight 相加即从 [-1, -3, 4] 变为 [4, -2, 5]。接下来从中选择权重最大的 Invoker 作为结果即节点 C。最后将节点 C 的 currentWeight 值减去 totalWeight 值最终得到 currentWeight 数组为 [4, -2, -2]。处理第六个请求currentWeight 数组中的权重与配置的 weight 相加即从 [4, -2, -2] 变为 [9, -1, -1]。接下来从中选择权重最大的 Invoker 作为结果即节点 A。最后将节点 A 的 currentWeight 值减去 totalWeight 值最终得到 currentWeight 数组为 [2, -1, -1]。处理第七个请求currentWeight 数组中的权重与配置的 weight 相加即从 [2, -1, -1] 变为 [7, 0, 0]。接下来从中选择权重最大的 Invoker 作为结果即节点 A。最后将节点 A 的 currentWeight 值减去 totalWeight 值最终得到 currentWeight 数组为 [0, 0, 0]。
到此为止一个轮询的周期就结束了。
而在 Dubbo 2.6.4 版本中上面示例的一次轮询结果是 [A, A, A, A, A, B, C]也就是说前 5 个请求会全部都落到 A 这个节点上。这将会使节点 A 在短时间内接收大量的请求压力陡增而节点 B 和节点 C 此时没有收到任何请求处于完全空闲的状态这种“瞬间分配不平衡”的情况也就是前面提到的“不平滑问题”。
在 RoundRobinLoadBalance 中我们为每个 Invoker 对象创建了一个对应的 WeightedRoundRobin 对象用来记录配置的权重weight 字段以及随每次负载均衡算法执行变化的 current 权重current 字段。
了解了 WeightedRoundRobin 这个内部类后我们再来看 RoundRobinLoadBalance.doSelect() 方法的具体实现
protected T InvokerT doSelect(ListInvokerT invokers, URL url, Invocation invocation) {String key invokers.get(0).getUrl().getServiceKey() . invocation.getMethodName();// 获取整个Invoker列表对应的WeightedRoundRobin映射表如果为空则创建一个新的WeightedRoundRobin映射表ConcurrentMapString, WeightedRoundRobin map methodWeightMap.computeIfAbsent(key, k - new ConcurrentHashMap());int totalWeight 0;long maxCurrent Long.MIN_VALUE;long now System.currentTimeMillis(); // 获取当前时间InvokerT selectedInvoker null;WeightedRoundRobin selectedWRR null;for (InvokerT invoker : invokers) {String identifyString invoker.getUrl().toIdentityString();int weight getWeight(invoker, invocation);// 检测当前Invoker是否有相应的WeightedRoundRobin对象没有则进行创建WeightedRoundRobin weightedRoundRobin map.computeIfAbsent(identifyString, k - {WeightedRoundRobin wrr new WeightedRoundRobin();wrr.setWeight(weight);return wrr;});// 检测Invoker权重是否发生了变化若发生变化则更新WeightedRoundRobin的weight字段if (weight ! weightedRoundRobin.getWeight()) {weightedRoundRobin.setWeight(weight);}// 让currentWeight加上配置的Weightlong cur weightedRoundRobin.increaseCurrent();// 设置lastUpdate字段weightedRoundRobin.setLastUpdate(now);// 寻找具有最大currentWeight的Invoker以及Invoker对应的WeightedRoundRobinif (cur maxCurrent) {maxCurrent cur;selectedInvoker invoker;selectedWRR weightedRoundRobin;}totalWeight weight; // 计算权重总和}if (invokers.size() ! map.size()) {map.entrySet().removeIf(item - now - item.getValue().getLastUpdate() RECYCLE_PERIOD);}if (selectedInvoker ! null) {// 用currentWeight减去totalWeightselectedWRR.sel(totalWeight);// 返回选中的Invoker对象return selectedInvoker;}return invokers.get(0);
}ShortestResponseLoadBalance 最短响应时间
ShortestResponseLoadBalance 是Dubbo 2.7 版本之后新增加的一个 LoadBalance 实现类。它实现了最短响应时间的负载均衡算法也就是从多个 Provider 节点中选出调用成功的且响应时间最短的 Provider 节点不过满足该条件的 Provider 节点可能有多个所以还要再使用随机算法进行一次选择得到最终要调用的 Provider 节点。
了解了 ShortestResponseLoadBalance 的核心原理之后我们一起来看 ShortestResponseLoadBalance.doSelect() 方法的核心实现如下所示
protected T InvokerT doSelect(ListInvokerT invokers, URL url, Invocation invocation) {// 记录Invoker集合的数量int length invokers.size();// 用于记录所有Invoker集合中最短响应时间long shortestResponse Long.MAX_VALUE;// 具有相同最短响应时间的Invoker个数int shortestCount 0;// 存放所有最短响应时间的Invoker的下标int[] shortestIndexes new int[length];// 存储每个Invoker的权重int[] weights new int[length];// 存储权重总和int totalWeight 0;// 记录第一个Invoker对象的权重int firstWeight 0;// 最短响应时间Invoker集合中的Invoker权重是否相同boolean sameWeight true;for (int i 0; i length; i) {InvokerT invoker invokers.get(i);RpcStatus rpcStatus RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName());// 获取调用成功的平均时间具体计算方式是调用成功的请求数总数对应的总耗时 / 调用成功的请求数总数 成功调用的平均时间// RpcStatus 的内容在前面课时已经介绍过了这里不再重复long succeededAverageElapsed rpcStatus.getSucceededAverageElapsed();// 获取的是该Provider当前的活跃请求数也就是当前正在处理的请求数int active rpcStatus.getActive();// 计算一个处理新请求的预估值也就是如果当前请求发给这个Provider大概耗时多久处理完成long estimateResponse succeededAverageElapsed * active;// 计算该Invoker的权重主要是处理预热int afterWarmup getWeight(invoker, invocation);weights[i] afterWarmup;if (estimateResponse shortestResponse) { // 第一次找到Invoker集合中最短响应耗时的Invoker对象记录其相关信息shortestResponse estimateResponse;shortestCount 1;shortestIndexes[0] i;totalWeight afterWarmup;firstWeight afterWarmup;sameWeight true;} else if (estimateResponse shortestResponse) {// 出现多个耗时最短的Invoker对象shortestIndexes[shortestCount] i;totalWeight afterWarmup;if (sameWeight i 0 afterWarmup ! firstWeight) {sameWeight false;}}}if (shortestCount 1) {return invokers.get(shortestIndexes[0]);}// 如果耗时最短的所有Invoker对象的权重不相同则通过加权随机负载均衡的方式选择一个Invoker返回if (!sameWeight totalWeight 0) {int offsetWeight ThreadLocalRandom.current().nextInt(totalWeight);for (int i 0; i shortestCount; i) {int shortestIndex shortestIndexes[i];offsetWeight - weights[shortestIndex];if (offsetWeight 0) {return invokers.get(shortestIndex);}}}// 如果耗时最短的所有Invoker对象的权重相同则随机返回一个return invokers.get(shortestIndexes[ThreadLocalRandom.current().nextInt(shortestCount)]);
}总结
我们紧接上一课时介绍了 LoadBalance 接口的剩余三个实现。
我们首先介绍了
LeastActiveLoadBalance 实现它使用最小活跃数负载均衡算法选择当前请求最少的 Provider 节点处理最新的请求接下来介绍了 RoundRobinLoadBalance 实现它使用加权轮询负载均衡算法弥补了单纯的轮询负载均衡算法导致的问题同时随着 Dubbo 版本的升级也将其自身不够平滑的问题优化掉了最后介绍了 ShortestResponseLoadBalance 实现它会从响应时间最短的 Provider 节点中选择一个 Provider 节点来处理新请求。