learn.lianglianglee.com/专栏/Dubbo源码解读与实战-完/36 负载均衡:公平公正物尽其用的负载均衡策略,这里都有(下).md.html
2022-09-06 22:30:37 +08:00

555 lines
36 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<link rel="icon" href="/static/favicon.png">
<title>36 负载均衡:公平公正物尽其用的负载均衡策略,这里都有(下).md.html</title>
<!-- Spectre.css framework -->
<link rel="stylesheet" href="/static/index.css">
<!-- theme css & js -->
<meta name="generator" content="Hexo 4.2.0">
</head>
<body>
<div class="book-container">
<div class="book-sidebar">
<div class="book-brand">
<a href="/">
<img src="/static/favicon.png">
<span>技术文章摘抄</span>
</a>
</div>
<div class="book-menu uncollapsible">
<ul class="uncollapsible">
<li><a href="/" class="current-tab">首页</a></li>
</ul>
<ul class="uncollapsible">
<li><a href="../">上一级</a></li>
</ul>
<ul class="uncollapsible">
<li>
<a href="/专栏/Dubbo源码解读与实战-完/00 开篇词 深入掌握 Dubbo 原理与实现,提升你的职场竞争力.md.html">00 开篇词 深入掌握 Dubbo 原理与实现,提升你的职场竞争力</a>
</li>
<li>
<a href="/专栏/Dubbo源码解读与实战-完/01 Dubbo 源码环境搭建:千里之行,始于足下.md.html">01 Dubbo 源码环境搭建:千里之行,始于足下</a>
</li>
<li>
<a href="/专栏/Dubbo源码解读与实战-完/02 Dubbo 的配置总线:抓住 URL就理解了半个 Dubbo.md.html">02 Dubbo 的配置总线:抓住 URL就理解了半个 Dubbo</a>
</li>
<li>
<a href="/专栏/Dubbo源码解读与实战-完/03 Dubbo SPI 精析,接口实现两极反转(上).md.html">03 Dubbo SPI 精析,接口实现两极反转(上)</a>
</li>
<li>
<a href="/专栏/Dubbo源码解读与实战-完/04 Dubbo SPI 精析,接口实现两极反转(下).md.html">04 Dubbo SPI 精析,接口实现两极反转(下)</a>
</li>
<li>
<a href="/专栏/Dubbo源码解读与实战-完/05 海量定时任务,一个时间轮搞定.md.html">05 海量定时任务,一个时间轮搞定</a>
</li>
<li>
<a href="/专栏/Dubbo源码解读与实战-完/06 ZooKeeper 与 Curator求你别用 ZkClient 了(上).md.html">06 ZooKeeper 与 Curator求你别用 ZkClient 了(上)</a>
</li>
<li>
<a href="/专栏/Dubbo源码解读与实战-完/07 ZooKeeper 与 Curator求你别用 ZkClient 了(下).md.html">07 ZooKeeper 与 Curator求你别用 ZkClient 了(下)</a>
</li>
<li>
<a href="/专栏/Dubbo源码解读与实战-完/08 代理模式与常见实现.md.html">08 代理模式与常见实现</a>
</li>
<li>
<a href="/专栏/Dubbo源码解读与实战-完/09 Netty 入门,用它做网络编程都说好(上).md.html">09 Netty 入门,用它做网络编程都说好(上)</a>
</li>
<li>
<a href="/专栏/Dubbo源码解读与实战-完/10 Netty 入门,用它做网络编程都说好(下).md.html">10 Netty 入门,用它做网络编程都说好(下)</a>
</li>
<li>
<a href="/专栏/Dubbo源码解读与实战-完/11 简易版 RPC 框架实现(上).md.html">11 简易版 RPC 框架实现(上)</a>
</li>
<li>
<a href="/专栏/Dubbo源码解读与实战-完/12 简易版 RPC 框架实现(下).md.html">12 简易版 RPC 框架实现(下)</a>
</li>
<li>
<a href="/专栏/Dubbo源码解读与实战-完/13 本地缓存:降低 ZooKeeper 压力的一个常用手段.md.html">13 本地缓存:降低 ZooKeeper 压力的一个常用手段</a>
</li>
<li>
<a href="/专栏/Dubbo源码解读与实战-完/14 重试机制是网络操作的基本保证.md.html">14 重试机制是网络操作的基本保证</a>
</li>
<li>
<a href="/专栏/Dubbo源码解读与实战-完/15 ZooKeeper 注册中心实现,官方推荐注册中心实践.md.html">15 ZooKeeper 注册中心实现,官方推荐注册中心实践</a>
</li>
<li>
<a href="/专栏/Dubbo源码解读与实战-完/16 Dubbo Serialize 层:多种序列化算法,总有一款适合你.md.html">16 Dubbo Serialize 层:多种序列化算法,总有一款适合你</a>
</li>
<li>
<a href="/专栏/Dubbo源码解读与实战-完/17 Dubbo Remoting 层核心接口分析:这居然是一套兼容所有 NIO 框架的设计?.md.html">17 Dubbo Remoting 层核心接口分析:这居然是一套兼容所有 NIO 框架的设计?</a>
</li>
<li>
<a href="/专栏/Dubbo源码解读与实战-完/18 Buffer 缓冲区:我们不生产数据,我们只是数据的搬运工.md.html">18 Buffer 缓冲区:我们不生产数据,我们只是数据的搬运工</a>
</li>
<li>
<a href="/专栏/Dubbo源码解读与实战-完/19 Transporter 层核心实现:编解码与线程模型一文打尽(上).md.html">19 Transporter 层核心实现:编解码与线程模型一文打尽(上)</a>
</li>
<li>
<a href="/专栏/Dubbo源码解读与实战-完/20 Transporter 层核心实现:编解码与线程模型一文打尽(下).md.html">20 Transporter 层核心实现:编解码与线程模型一文打尽(下)</a>
</li>
<li>
<a href="/专栏/Dubbo源码解读与实战-完/21 Exchange 层剖析:彻底搞懂 Request-Response 模型(上).md.html">21 Exchange 层剖析:彻底搞懂 Request-Response 模型(上)</a>
</li>
<li>
<a href="/专栏/Dubbo源码解读与实战-完/22 Exchange 层剖析:彻底搞懂 Request-Response 模型(下).md.html">22 Exchange 层剖析:彻底搞懂 Request-Response 模型(下)</a>
</li>
<li>
<a href="/专栏/Dubbo源码解读与实战-完/23 核心接口介绍RPC 层骨架梳理.md.html">23 核心接口介绍RPC 层骨架梳理</a>
</li>
<li>
<a href="/专栏/Dubbo源码解读与实战-完/24 从 Protocol 起手,看服务暴露和服务引用的全流程(上).md.html">24 从 Protocol 起手,看服务暴露和服务引用的全流程(上)</a>
</li>
<li>
<a href="/专栏/Dubbo源码解读与实战-完/25 从 Protocol 起手,看服务暴露和服务引用的全流程(下).md.html">25 从 Protocol 起手,看服务暴露和服务引用的全流程(下)</a>
</li>
<li>
<a href="/专栏/Dubbo源码解读与实战-完/26 加餐:直击 Dubbo “心脏”,带你一起探秘 Invoker.md.html">26 加餐:直击 Dubbo “心脏”,带你一起探秘 Invoker</a>
</li>
<li>
<a href="/专栏/Dubbo源码解读与实战-完/27 加餐:直击 Dubbo “心脏”,带你一起探秘 Invoker.md.html">27 加餐:直击 Dubbo “心脏”,带你一起探秘 Invoker</a>
</li>
<li>
<a href="/专栏/Dubbo源码解读与实战-完/28 复杂问题简单化,代理帮你隐藏了多少底层细节?.md.html">28 复杂问题简单化,代理帮你隐藏了多少底层细节?</a>
</li>
<li>
<a href="/专栏/Dubbo源码解读与实战-完/29 加餐HTTP 协议 + JSON-RPCDubbo 跨语言就是如此简单.md.html">29 加餐HTTP 协议 + JSON-RPCDubbo 跨语言就是如此简单</a>
</li>
<li>
<a href="/专栏/Dubbo源码解读与实战-完/30 Filter 接口,扩展 Dubbo 框架的常用手段指北.md.html">30 Filter 接口,扩展 Dubbo 框架的常用手段指北</a>
</li>
<li>
<a href="/专栏/Dubbo源码解读与实战-完/31 加餐:深潜 Directory 实现,探秘服务目录玄机.md.html">31 加餐:深潜 Directory 实现,探秘服务目录玄机</a>
</li>
<li>
<a href="/专栏/Dubbo源码解读与实战-完/32 路由机制:请求到底怎么走,它说了算(上).md.html">32 路由机制:请求到底怎么走,它说了算(上)</a>
</li>
<li>
<a href="/专栏/Dubbo源码解读与实战-完/33 路由机制:请求到底怎么走,它说了算(下).md.html">33 路由机制:请求到底怎么走,它说了算(下)</a>
</li>
<li>
<a href="/专栏/Dubbo源码解读与实战-完/34 加餐:初探 Dubbo 动态配置的那些事儿.md.html">34 加餐:初探 Dubbo 动态配置的那些事儿</a>
</li>
<li>
<a href="/专栏/Dubbo源码解读与实战-完/35 负载均衡:公平公正物尽其用的负载均衡策略,这里都有(上).md.html">35 负载均衡:公平公正物尽其用的负载均衡策略,这里都有(上)</a>
</li>
<li>
<a class="current-tab" href="/专栏/Dubbo源码解读与实战-完/36 负载均衡:公平公正物尽其用的负载均衡策略,这里都有(下).md.html">36 负载均衡:公平公正物尽其用的负载均衡策略,这里都有(下)</a>
</li>
<li>
<a href="/专栏/Dubbo源码解读与实战-完/37 集群容错:一个好汉三个帮(上).md.html">37 集群容错:一个好汉三个帮(上)</a>
</li>
<li>
<a href="/专栏/Dubbo源码解读与实战-完/38 集群容错:一个好汉三个帮(下).md.html">38 集群容错:一个好汉三个帮(下)</a>
</li>
<li>
<a href="/专栏/Dubbo源码解读与实战-完/39 加餐多个返回值不用怕Merger 合并器来帮忙.md.html">39 加餐多个返回值不用怕Merger 合并器来帮忙</a>
</li>
<li>
<a href="/专栏/Dubbo源码解读与实战-完/40 加餐模拟远程调用Mock 机制帮你搞定.md.html">40 加餐模拟远程调用Mock 机制帮你搞定</a>
</li>
<li>
<a href="/专栏/Dubbo源码解读与实战-完/41 加餐:一键通关服务发布全流程.md.html">41 加餐:一键通关服务发布全流程</a>
</li>
<li>
<a href="/专栏/Dubbo源码解读与实战-完/42 加餐:服务引用流程全解析.md.html">42 加餐:服务引用流程全解析</a>
</li>
<li>
<a href="/专栏/Dubbo源码解读与实战-完/43 服务自省设计方案:新版本新方案.md.html">43 服务自省设计方案:新版本新方案</a>
</li>
<li>
<a href="/专栏/Dubbo源码解读与实战-完/44 元数据方案深度剖析,如何避免注册中心数据量膨胀?.md.html">44 元数据方案深度剖析,如何避免注册中心数据量膨胀?</a>
</li>
<li>
<a href="/专栏/Dubbo源码解读与实战-完/45 加餐:深入服务自省方案中的服务发布订阅(上).md.html">45 加餐:深入服务自省方案中的服务发布订阅(上)</a>
</li>
<li>
<a href="/专栏/Dubbo源码解读与实战-完/46 加餐:深入服务自省方案中的服务发布订阅(下).md.html">46 加餐:深入服务自省方案中的服务发布订阅(下)</a>
</li>
<li>
<a href="/专栏/Dubbo源码解读与实战-完/47 配置中心设计与实现:集中化配置 and 本地化配置,我都要(上).md.html">47 配置中心设计与实现:集中化配置 and 本地化配置,我都要(上)</a>
</li>
<li>
<a href="/专栏/Dubbo源码解读与实战-完/48 配置中心设计与实现:集中化配置 and 本地化配置,我都要(下).md.html">48 配置中心设计与实现:集中化配置 and 本地化配置,我都要(下)</a>
</li>
<li>
<a href="/专栏/Dubbo源码解读与实战-完/49 结束语 认真学习,缩小差距.md.html">49 结束语 认真学习,缩小差距</a>
</li>
</ul>
</div>
</div>
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
<div class="sidebar-toggle-inner"></div>
</div>
<script>
function add_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.add('show')
}
function remove_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.remove('show')
}
function sidebar_toggle() {
let sidebar_toggle = document.querySelector('.sidebar-toggle')
let sidebar = document.querySelector('.book-sidebar')
let content = document.querySelector('.off-canvas-content')
if (sidebar_toggle.classList.contains('extend')) { // show
sidebar_toggle.classList.remove('extend')
sidebar.classList.remove('hide')
content.classList.remove('extend')
} else { // hide
sidebar_toggle.classList.add('extend')
sidebar.classList.add('hide')
content.classList.add('extend')
}
}
function open_sidebar() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.add('show')
overlay.classList.add('show')
}
function hide_canvas() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.remove('show')
overlay.classList.remove('show')
}
</script>
<div class="off-canvas-content">
<div class="columns">
<div class="column col-12 col-lg-12">
<div class="book-navbar">
<!-- For Responsive Layout -->
<header class="navbar">
<section class="navbar-section">
<a onclick="open_sidebar()">
<i class="icon icon-menu"></i>
</a>
</section>
</header>
</div>
<div class="book-content" style="max-width: 960px; margin: 0 auto;
overflow-x: auto;
overflow-y: hidden;">
<div class="book-post">
<p id="tip" align="center"></p>
<div><h1>36 负载均衡:公平公正物尽其用的负载均衡策略,这里都有(下)</h1>
<p>在上一课时我们了解了 LoadBalance 接口定义以及 AbstractLoadBalance 抽象类的内容,还详细介绍了 ConsistentHashLoadBalance 以及 RandomLoadBalance 这两个实现类的核心原理和大致实现。本课时我们将继续介绍 LoadBalance 的剩余三个实现。</p>
<h3>LeastActiveLoadBalance</h3>
<p>LeastActiveLoadBalance 使用的是<strong>最小活跃数负载均衡算法</strong>。它认为当前活跃请求数越小的 Provider 节点,剩余的处理能力越多,处理请求的效率也就越高,那么该 Provider 在单位时间内就可以处理更多的请求,所以我们应该优先将请求分配给该 Provider 节点。</p>
<p>LeastActiveLoadBalance 需要配合 ActiveLimitFilter 使用ActiveLimitFilter 会记录每个接口方法的活跃请求数,在 LeastActiveLoadBalance 进行负载均衡时,只会从活跃请求数最少的 Invoker 集合里挑选 Invoker。</p>
<p>在 LeastActiveLoadBalance 的实现中,首先会选出所有活跃请求数最小的 Invoker 对象,之后的逻辑与 RandomLoadBalance 完全一样,即按照这些 Invoker 对象的权重挑选最终的 Invoker 对象。下面是 LeastActiveLoadBalance.doSelect() 方法的具体实现:</p>
<pre><code>protected &lt;T&gt; Invoker&lt;T&gt; doSelect(List&lt;Invoker&lt;T&gt;&gt; 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 &lt; length; i++) { // 遍历所有Invoker获取活跃请求数最小的Invoker集合
Invoker&lt;T&gt; 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 &lt; leastActive) {
// 当前的Invoker是第一个活跃请求数最小的Invoker则记录如下信息
leastActive = active; // 重新记录最小的活跃请求数
leastCount = 1; // 重新记录活跃请求数最小的Invoker集合个数
leastIndexes[0] = i; // 重新记录Invoker
totalWeight = afterWarmup; // 重新记录总权重值
firstWeight = afterWarmup; // 该Invoker作为第一个Invoker记录其权重值
sameWeight = true; // 重新记录是否权重值相等
} else if (active == leastActive) {
// 当前Invoker属于活跃请求数最小的Invoker集合
leastIndexes[leastCount++] = i; // 记录该Invoker的下标
totalWeight += afterWarmup; // 更新总权重
if (sameWeight &amp;&amp; afterWarmup != firstWeight) {
sameWeight = false; // 更新权重值是否相等
}
}
}
// 如果只有一个活跃请求数最小的Invoker对象直接返回即可
if (leastCount == 1) {
return invokers.get(leastIndexes[0]);
}
// 下面按照RandomLoadBalance的逻辑从活跃请求数最小的Invoker集合中随机选择一个Invoker对象返回
if (!sameWeight &amp;&amp; totalWeight &gt; 0) {
int offsetWeight = ThreadLocalRandom.current().nextInt(totalWeight);
for (int i = 0; i &lt; leastCount; i++) {
int leastIndex = leastIndexes[i];
offsetWeight -= weights[leastIndex];
if (offsetWeight &lt; 0) {
return invokers.get(leastIndex);
}
}
}
return invokers.get(leastIndexes[ThreadLocalRandom.current().nextInt(leastCount)]);
}
</code></pre>
<p>ActiveLimitFilter 以及底层的 RpcStatus 记录活跃请求数的具体原理,在前面的[第 30 课时]中我们已经详细分析过了,这里不再重复,如果有不清楚的地方,你可以回顾之前课时相关的内容。</p>
<h3>RoundRobinLoadBalance</h3>
<p>RoundRobinLoadBalance 实现的是<strong>加权轮询负载均衡算法</strong></p>
<p>轮询指的是将请求轮流分配给每个 Provider。例如有 A、B、C 三个 Provider 节点,按照普通轮询的方式,我们会将第一个请求分配给 Provider A将第二个请求分配给 Provider B第三个请求分配给 Provider C第四个请求再次分配给 Provider A……如此循环往复。</p>
<p><strong>轮询是一种无状态负载均衡算法,实现简单,适用于集群中所有 Provider 节点性能相近的场景。</strong> 但现实情况中就很难保证这一点了,因为很容易出现集群中性能最好和最差的 Provider 节点处理同样流量的情况,这就可能导致性能差的 Provider 节点各方面资源非常紧张,甚至无法及时响应了,但是性能好的 Provider 节点的各方面资源使用还较为空闲。这时我们可以通过加权轮询的方式,降低分配到性能较差的 Provider 节点的流量。</p>
<p>加权之后,分配给每个 Provider 节点的流量比会接近或等于它们的权重比。例如Provider 节点 A、B、C 权重比为 5:1:1那么在 7 次请求中,节点 A 将收到 5 次请求,节点 B 会收到 1 次请求,节点 C 则会收到 1 次请求。</p>
<p><strong>在 Dubbo 2.6.4 版本及之前RoundRobinLoadBalance 的实现存在一些问题,例如,选择 Invoker 的性能问题、负载均衡时不够平滑等。在 Dubbo 2.6.5 版本之后,这些问题都得到了修复</strong>,所以这里我们就来介绍最新的 RoundRobinLoadBalance 实现。</p>
<p>每个 Provider 节点有两个权重:一个权重是配置的 weight该值在负载均衡的过程中不会变化另一个权重是 currentWeight该值会在负载均衡的过程中动态调整初始值为 0。</p>
<p>当有新的请求进来时RoundRobinLoadBalance 会遍历 Invoker 列表,并用对应的 currentWeight 加上其配置的权重。遍历完成后,再找到最大的 currentWeight将其减去权重总和然后返回相应的 Invoker 对象。</p>
<p>下面我们通过一个示例说明 RoundRobinLoadBalance 的执行流程,这里我们依旧假设 A、B、C 三个节点的权重比例为 5:1:1。</p>
<p><img src="assets/CgqCHl_ArGSAfxA6AAHyWL4Af1o908.png" alt="png" /></p>
<ol>
<li>处理第一个请求currentWeight 数组中的权重与配置的 weight 相加,即从 [0, 0, 0] 变为 [5, 1, 1]。接下来,从中选择权重最大的 Invoker 作为结果,即节点 A。最后将节点 A 的 currentWeight 值减去 totalWeight 值,最终得到 currentWeight 数组为 [-2, 1, 1]。</li>
<li>处理第二个请求currentWeight 数组中的权重与配置的 weight 相加,即从 [-2, 1, 1] 变为 [3, 2, 2]。接下来,从中选择权重最大的 Invoker 作为结果,即节点 A。最后将节点 A 的 currentWeight 值减去 totalWeight 值,最终得到 currentWeight 数组为 [-4, 2, 2]。</li>
<li>处理第三个请求currentWeight 数组中的权重与配置的 weight 相加,即从 [-4, 2, 2] 变为 [1, 3, 3]。接下来,从中选择权重最大的 Invoker 作为结果,即节点 B。最后将节点 B 的 currentWeight 值减去 totalWeight 值,最终得到 currentWeight 数组为 [1, -4, 3]。</li>
<li>处理第四个请求currentWeight 数组中的权重与配置的 weight 相加,即从 [1, -4, 3] 变为 [6, -3, 4]。接下来,从中选择权重最大的 Invoker 作为结果,即节点 A。最后将节点 A 的 currentWeight 值减去 totalWeight 值,最终得到 currentWeight 数组为 [-1, -3, 4]。</li>
<li>处理第五个请求currentWeight 数组中的权重与配置的 weight 相加,即从 [-1, -3, 4] 变为 [4, -2, 5]。接下来,从中选择权重最大的 Invoker 作为结果,即节点 C。最后将节点 C 的 currentWeight 值减去 totalWeight 值,最终得到 currentWeight 数组为 [4, -2, -2]。</li>
<li>处理第六个请求currentWeight 数组中的权重与配置的 weight 相加,即从 [4, -2, -2] 变为 [9, -1, -1]。接下来,从中选择权重最大的 Invoker 作为结果,即节点 A。最后将节点 A 的 currentWeight 值减去 totalWeight 值,最终得到 currentWeight 数组为 [2, -1, -1]。</li>
<li>处理第七个请求currentWeight 数组中的权重与配置的 weight 相加,即从 [2, -1, -1] 变为 [7, 0, 0]。接下来,从中选择权重最大的 Invoker 作为结果,即节点 A。最后将节点 A 的 currentWeight 值减去 totalWeight 值,最终得到 currentWeight 数组为 [0, 0, 0]。</li>
</ol>
<p>到此为止,一个轮询的周期就结束了。</p>
<p>而在 Dubbo 2.6.4 版本中,上面示例的一次轮询结果是 [A, A, A, A, A, B, C],也就是说前 5 个请求会全部都落到 A 这个节点上。这将会使节点 A 在短时间内接收大量的请求,压力陡增,而节点 B 和节点 C 此时没有收到任何请求,处于完全空闲的状态,这种“瞬间分配不平衡”的情况也就是前面提到的“不平滑问题”。</p>
<p>在 RoundRobinLoadBalance 中,我们<strong>为每个 Invoker 对象创建了一个对应的 WeightedRoundRobin 对象</strong>用来记录配置的权重weight 字段)以及随每次负载均衡算法执行变化的 current 权重current 字段)。</p>
<p>了解了 WeightedRoundRobin 这个内部类后,我们再来看 RoundRobinLoadBalance.doSelect() 方法的具体实现:</p>
<pre><code>protected &lt;T&gt; Invoker&lt;T&gt; doSelect(List&lt;Invoker&lt;T&gt;&gt; invokers, URL url, Invocation invocation) {
String key = invokers.get(0).getUrl().getServiceKey() + &quot;.&quot; + invocation.getMethodName();
// 获取整个Invoker列表对应的WeightedRoundRobin映射表如果为空则创建一个新的WeightedRoundRobin映射表
ConcurrentMap&lt;String, WeightedRoundRobin&gt; map = methodWeightMap.computeIfAbsent(key, k -&gt; new ConcurrentHashMap&lt;&gt;());
int totalWeight = 0;
long maxCurrent = Long.MIN_VALUE;
long now = System.currentTimeMillis(); // 获取当前时间
Invoker&lt;T&gt; selectedInvoker = null;
WeightedRoundRobin selectedWRR = null;
for (Invoker&lt;T&gt; invoker : invokers) {
String identifyString = invoker.getUrl().toIdentityString();
int weight = getWeight(invoker, invocation);
// 检测当前Invoker是否有相应的WeightedRoundRobin对象没有则进行创建
WeightedRoundRobin weightedRoundRobin = map.computeIfAbsent(identifyString, k -&gt; {
WeightedRoundRobin wrr = new WeightedRoundRobin();
wrr.setWeight(weight);
return wrr;
});
// 检测Invoker权重是否发生了变化若发生变化则更新WeightedRoundRobin的weight字段
if (weight != weightedRoundRobin.getWeight()) {
weightedRoundRobin.setWeight(weight);
}
// 让currentWeight加上配置的Weight
long cur = weightedRoundRobin.increaseCurrent();
// 设置lastUpdate字段
weightedRoundRobin.setLastUpdate(now);
// 寻找具有最大currentWeight的Invoker以及Invoker对应的WeightedRoundRobin
if (cur &gt; maxCurrent) {
maxCurrent = cur;
selectedInvoker = invoker;
selectedWRR = weightedRoundRobin;
}
totalWeight += weight; // 计算权重总和
}
if (invokers.size() != map.size()) {
map.entrySet().removeIf(item -&gt; now - item.getValue().getLastUpdate() &gt; RECYCLE_PERIOD);
}
if (selectedInvoker != null) {
// 用currentWeight减去totalWeight
selectedWRR.sel(totalWeight);
// 返回选中的Invoker对象
return selectedInvoker;
}
return invokers.get(0);
}
</code></pre>
<h3>ShortestResponseLoadBalance</h3>
<p>ShortestResponseLoadBalance 是<strong>Dubbo 2.7 版本之后新增加的一个 LoadBalance 实现类</strong>。它实现了<strong>最短响应时间的负载均衡算法</strong>,也就是从多个 Provider 节点中选出调用成功的且响应时间最短的 Provider 节点,不过满足该条件的 Provider 节点可能有多个,所以还要再使用随机算法进行一次选择,得到最终要调用的 Provider 节点。</p>
<p>了解了 ShortestResponseLoadBalance 的核心原理之后,我们一起来看 ShortestResponseLoadBalance.doSelect() 方法的核心实现,如下所示:</p>
<pre><code>protected &lt;T&gt; Invoker&lt;T&gt; doSelect(List&lt;Invoker&lt;T&gt;&gt; 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 &lt; length; i++) {
Invoker&lt;T&gt; 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 &lt; 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 &amp;&amp; i &gt; 0
&amp;&amp; afterWarmup != firstWeight) {
sameWeight = false;
}
}
}
if (shortestCount == 1) {
return invokers.get(shortestIndexes[0]);
}
// 如果耗时最短的所有Invoker对象的权重不相同则通过加权随机负载均衡的方式选择一个Invoker返回
if (!sameWeight &amp;&amp; totalWeight &gt; 0) {
int offsetWeight = ThreadLocalRandom.current().nextInt(totalWeight);
for (int i = 0; i &lt; shortestCount; i++) {
int shortestIndex = shortestIndexes[i];
offsetWeight -= weights[shortestIndex];
if (offsetWeight &lt; 0) {
return invokers.get(shortestIndex);
}
}
}
// 如果耗时最短的所有Invoker对象的权重相同则随机返回一个
return invokers.get(shortestIndexes[ThreadLocalRandom.current().nextInt(shortestCount)]);
}
</code></pre>
<h3>总结</h3>
<p>今天我们紧接上一课时介绍了 LoadBalance 接口的剩余三个实现。</p>
<p>我们首先介绍了 LeastActiveLoadBalance 实现,它使用最小活跃数负载均衡算法,选择当前请求最少的 Provider 节点处理最新的请求;接下来介绍了 RoundRobinLoadBalance 实现,它使用加权轮询负载均衡算法,弥补了单纯的轮询负载均衡算法导致的问题,同时随着 Dubbo 版本的升级,也将其自身不够平滑的问题优化掉了;最后介绍了 ShortestResponseLoadBalance 实现,它会从响应时间最短的 Provider 节点中选择一个 Provider 节点来处理新请求。</p>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/Dubbo源码解读与实战-完/35 负载均衡:公平公正物尽其用的负载均衡策略,这里都有(上).md.html">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/Dubbo源码解读与实战-完/37 集群容错:一个好汉三个帮(上).md.html">下一页</a>
</div>
</div>
</div>
</div>
</div>
</div>
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
</div>
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"70996f6969033d60","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
</body>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-NPSEEVD756');
var path = window.location.pathname
var cookie = getCookie("lastPath");
console.log(path)
if (path.replace("/", "") === "") {
if (cookie.replace("/", "") !== "") {
console.log(cookie)
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
}
} else {
setCookie("lastPath", path)
}
function setCookie(cname, cvalue) {
var d = new Date();
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
}
return "";
}
</script>
</html>