首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Dubbo 负载均衡全解析:六种策略的调用链、行为与历史教训

Dubbo 负载均衡全解析:六种策略的调用链、行为与历史教训

作者头像
阿特拉斯
发布2026-07-06 12:32:21
发布2026-07-06 12:32:21
20
举报

概览

Dubbo 的负载均衡是一个 SPI 扩展点 org.apache.dubbo.rpc.cluster.LoadBalance,默认实现是 random。官方注册了六种策略:

名称

实现类

一句话定位

random

RandomLoadBalance

无状态加权随机,默认兜底

roundrobin

RoundRobinLoadBalance

平滑加权轮询

leastactive

LeastActiveLoadBalance

选活跃请求数最少的节点

shortestresponse

ShortestResponseLoadBalance

估算响应最短,滑动窗口

consistenthash

ConsistentHashLoadBalance

一致性哈希,保 key 亲和

adaptive

AdaptiveLoadBalance

P2C + provider 指标 + EWMA

读完这篇你能搞清三件事:

1. 一次 RPC 调用里,负载均衡到底在哪个环节被触发,前后发生了什么。

2. 六种策略各自的真实行为、长处和代价——结合代码和历史 commit 看取舍,不做文档式罗列。

3. 为什么 Dubbo 没有独立的 ewma 策略,以及为什么给 consistenthash 加「过载转移」的尝试被回退了。

这篇基于 Dubbo 源码与 git 历史的分析记录,行文偏工程笔记,结论给得比较克制——选型没有银弹,历史教训比「哪个最好」更有参考价值。


一、调用链:负载均衡在哪一步生效

负载均衡并不是一个独立的步骤,它嵌在集群调用器(Cluster Invoker)的选路逻辑里。消费者侧正常调用路径如下:

Dubbo 消费者侧负载均衡调用链
Dubbo 消费者侧负载均衡调用链

第 ④ 步 select(...) 内部还有一层决策,放大看:

select 内部决策放大图
select 内部决策放大图

调用链上有两个常被忽略的点。

一是候选集是路由之后的列表:Directory + Router 会先按规则和可用性筛一轮,LoadBalance 只在剩下的 provider 里挑。所以「为什么某个节点一直没流量」这类问题,有时根因在 Router 而不在 LoadBalance。

二是集群容错策略调用 select 的方式不一样

FailfastClusterInvoker:选一次,调一次,失败即返回。

FailoverClusterInvoker:每次重试都重新选,并把已经调用过的 provider 列表传进去,尽量避开同一节点。

ForkingClusterInvoker:一次选多个 Invoker 并行调用,谁先成功用谁。


二、六种策略的真实行为

先用一张表给整体适配性,下面再逐个展开。

策略

适合

不适合

random

默认兜底、provider 容量接近、低复杂度系统

延迟或负载差异显著

roundrobin

provider 稳定、请求成本接近、要平滑分布

慢节点照样被均匀分到流量

leastactive

请求成本波动大、想避开当前繁忙节点

活跃数不等于真实响应成本

shortestresponse

对响应时间敏感、延迟有可见波动

冷启动和无样本时信号弱

consistenthash

缓存亲和、租户/会话/key 固定路由

热点 key 和动态负载均衡

adaptive

有指标反馈回路、想动态优化负载

指标反馈链路不可靠时退化

2.1 random:无状态的兜底

RandomLoadBalance 是无状态的。没有显式权重、没有 warmup 时间戳时,它在候选列表里均匀随机;有权重或 warmup 时,按累积权重做加权随机,候选较多时用二分查找。

长处

• 协调成本极低。

• 不维护 per-method / per-invoker 状态。

• provider 列表频繁变动时依然稳。

代价

• 短时间窗口内分布可能不均。

• 对延迟、活跃请求数、失败、负载都没有反应。

一句话:它的强项就是「什么都不依赖」,所以适合做默认值,也适合在拿不准时兜底。

2.2 roundrobin:平滑加权轮询

RoundRobinLoadBalance 用的是平滑加权轮询(smooth weighted round robin)。它为每个 service/method 维护一张 WeightedRoundRobin 表,按 invoker 标识做 key。每次选择:把当前权重加到该 invoker 的当前值上,挑当前值最大的那个,再从它身上减去总权重。

长处

• 加权分布稳定。

• 短时间窗口比 random 更平滑。

代价

• 热路径上要维护可变状态。

• provider 下线时要做缓存清理。

• 同样不感知延迟和活跃数。

注意:状态在热路径上共享,并发问题历史上出过。commit 317e62f73fFix concurrency problems in RpcStatus and RoundRobinLoadBalance (#5881))就专门修过这里的并发 bug。任何想新加状态型策略的人,这都是前车之鉴。

2.3 leastactive:选活跃数最少的

LeastActiveLoadBalance 扫一遍所有候选 Invoker,读 RpcStatus.active(对 invoker URL + method 维度),挑活跃数最低的那些;如果权重不同,再在并列者里做加权随机。

长处

• 能对在途请求压力做反应。

• 直接复用现有 RpcStatus 计数器,实现简单。

代价

• 活跃数不等于服务成本:一个慢请求占的活跃数和一个快请求一样,但实际负载差很多。

• 忽略成功延迟、失败和超时率。

• 要扫全部候选。

2.4 shortestresponse:滑动窗口估响应

ShortestResponseLoadBalance 用一个滑动窗口的成功耗时来估算响应成本,对每个 invoker/method 状态算:

estimated response = 当前窗口内成功调用的平均耗时 × (active + 1)

选估算值最小的,并列时加权随机。

长处

• 比 leastactive 更贴近真实延迟。

• 用滑动窗口而不是全量历史,避免老数据拖累。

代价

• 估算只基于成功调用,失败和超时压力没被直接体现。

• 冷启动或无样本节点估算值可能是 0。

• 要维护 per-status 的窗口偏移,并周期性重置。

它的历史能反映 Dubbo 的取舍演变:commit 6d2ba7ec7badd new loadbalance strategy (#6064))最初用全量成功平均耗时 × 活跃数;后来 commit ffb520908aoptmization about shortestResponseLoadBalance (#8441))改成滑动窗口 + 周期重置。项目显然认为全量平均太陈旧,不适合做负载均衡决策。

2.5 consistenthash:保 key 亲和

ConsistentHashLoadBalance 在方法级别构建带虚拟节点的选择器,对配置的入参做哈希,在环上找到第一个匹配的虚拟节点返回对应 Invoker。

长处

• 保 key 亲和。

• 适合缓存本地性、会话式路由。

代价

故意不感知负载——这是设计上的取舍,不是缺陷。

• 热 key 会把单个 provider 打爆。

• 选择器的缓存和重建对并发、对路由后 invoker 列表的 identity 很敏感。

这里有一条非常重要的历史教训,单独放到第四节讲。

2.6 adaptive:P2C + 多信号融合

AdaptiveLoadBalance 用的是 power-of-two choices(P2C):随机抽两个 Invoker,挑负载得分低的那个。负载得分来自 AdaptiveMetrics,而且它不只是选,还会往 invocation 的 attribute 和 attachment 里写信息,让 AdaptiveLoadBalanceFilter 在调用结束后采集响应时间和 provider 侧指标。

这意味着 adaptive 的正确性依赖的不止是 LoadBalance 这个类本身:

• 选择阶段:记录选中的 provider、开始时间、loadbalance=adaptive 标记,以及请求 provider 指标(memload)的 attachment。

• 调用结束后:AdaptiveLoadBalanceFilter 检查标记,成功就读响应 attachment、加上消费者侧响应时间、更新 AdaptiveMetrics;失败就给选中 provider 记一次错误请求。

如果消费者侧 filter 没生效、响应没带回 provider 指标、或者指标更新链路延迟/丢失,策略还在跑,但丢了部分信号。这时选路会退化成「P2C + 不完整/默认指标 + 权重兜底」,对某些部署环境而言,未必比 randomleastactiveshortestresponse 这些简单策略更可靠。

长处

• 比「全表扫描」类策略更现代。

• 同时用消费者侧耗时和 provider 上报指标。

• 把权重和超时也纳入负载比较。

代价

• 不是单类策略,正确性依赖 filter + 指标反馈回路。

• provider 指标是 best-effort、异步的。

• 动件多,边界 case 多。

commit d17160591cAdaptive loadbalance (#10745))一次性加上了 AdaptiveLoadBalanceAdaptiveLoadBalanceFilter 和测试;后来 commit 33f688e404Update AdaptiveLoadBalance.java (#12636))又修了 P2C 两两比较里的超时查找 bug。这条历史的提示是:自适应策略的契约面更大——invoker identity、超时查找、指标 key、listener 行为,每一样都得对。


三、为什么没有独立的 ewma 策略

Dubbo 确实用了 EWMA 风格的延迟平滑,但它是嵌在 adaptive 里的一个信号,没有暴露成独立的 ewma 实现。

AdaptiveMetrics 里有一个 beta 和一个 ewma 字段,更新 provider 指标时套的是经典 EWMA 公式:

Vt = beta × Vt-1 + (1 - beta) × lastLatency

但最终的自适应负载得分远不止延迟一项,它把 provider CPU/load、EWMA 延迟、在途请求数、消费者侧成功率、配置权重都揉了进去。简化形式:

load =

providerCPULoad

× (sqrt(ewma) + 1)

× (inflight + 1)

/ (successRatio × weight + 1)

所以 Dubbo 当前的形态更接近:

adaptive = P2C + provider load + EWMA 延迟 + inflight + 成功率 + 权重

而不是:

ewma = 仅延迟 EWMA

为什么不把「仅延迟 EWMA」单独做成主选自适应策略?几个实际原因:

1. 延迟 EWMA 单独用会漏信号:高在途数、高 provider CPU/负载、近期错误、恢复期低样本置信,这些它都看不到。

2. EWMA 需要调用结束反馈,没法纯在 LoadBalance#doSelect(...) 里实现,必须有类似 AdaptiveLoadBalanceFilter 的反馈回路。

3. shortestresponse 已经覆盖了部分响应时间敏感场景,用的是滑动窗口估算。

4. 再加一个独立的 EWMA 策略,等于又引入一个带缓存生命周期、并发、冷启动、method key 隔离问题的热路径实现。

结论:Dubbo 把 EWMA 当作更宽自适应模型里的一个信号,而不是把它当成完整的负载均衡策略。这点和很多人的预期不一样。


四、consistenthash 的两次教训:别动语义契约

consistenthash 的历史有两条值得记住的教训。

4.1 加过载转移的尝试被回退

commit fee0dbab0cImprove consistent hashing load balancing with a new algorithm (#8948))试过给一致性哈希加过载感知兜底:在环上选中节点后,如果请求计数超过某个均值阈值,就跳到下一个节点。

后来 commit 89b74fe7f1Revert #8948 (#10233))把这套行为回退掉了。

为什么?一致性哈希的价值就是 key 亲和。在同一个策略里加负载再分配,等于改了它的语义契约——用户选 consistenthash 是为了「同一个 key 永远落到同一个节点」,一旦这个保证被打破,缓存命中、会话亲和全乱。

教训很直接:如果想要「带过载感知的一致性哈希」,应该做一个新的显式策略(比如叫 boundedconsistenthash),而不是改 consistenthash 本身。

4.2 正确性和热路径成本反复冒头

consistenthash 在历史上反复修,集中在两条线:

正确性:commit caed64b2a6Fix #13750, ConsistentHashLoadBalance not base on arguments (#13753))修正了选择逻辑——它应该对入参做哈希,而不是对方法名。

热路径成本:commit c8a8946f89Fix the issue of high CPU load caused by continuously creating new ConsistentHashSelector instances under high concurrency (#15497))把选择器创建改成用 ConcurrentMap 的原子 compute;commit 8f2b4decdcEnd the 'read/write' combination operation early to improve performance (#15666))又加了 read-first 快路径,让常见情况不必进入 compute(...)

这几条 commit 合在一起说明一件事:负载均衡代码在非常热的路径上。多余的内存分配、map 写入、选择器重建,在高并发下都会被放大成可见的 CPU 开销。任何新策略都得把这条当作 API 契约的一部分来对待。


五、从历史里能抽出的几条工作观察

下面这些不是定论,是反复出现的模式给出的克制提示:

1. 新策略不应悄悄改老策略的语义consistenthash 尤其如此。

2. 热路径成本应被视为 API 契约的一部分。全表扫描、compute(...)、内存分配、加锁,都需要理由。

3. 纯消费者侧策略好部署,但只看得到本地观察;要感知 provider,就得有类似 adaptive 的反馈回路。

4. 状态要小心地按 key 维护和清理。provider 上下线、router 输出变化、method 级隔离,历史上都引发过修复。

5. 延迟感知要避免全量平均shortestresponse 的演变指向窗口或 EWMA 式衰减。


名词解释

文中涉及的部分 Dubbo 概念和术语,简要说明如下。

SPI(Service Provider Interface)

Dubbo 的扩展机制。通过 META-INF/dubbo/internal/ 下的配置文件声明接口与实现的映射,运行时按名称动态加载。LoadBalance 就是一个 SPI 扩展点,默认实现是 random

Invoker

Dubbo 对远程服务提供者的抽象封装。每个 provider 在消费者侧会被包装成一个 Invoker 对象,负载均衡就是在多个 Invoker 里选一个来发起调用。

Directory

服务目录,维护某个服务的所有可用 Invoker 列表。它会和注册中心同步,感知 provider 上下线。负载均衡拿到的候选集,就是 Directory 输出的列表。

Router

路由规则,在 Directory 输出的候选集上再做一次过滤。比如按区域、按标签、按条件筛掉不符合规则的 provider。路由发生在负载均衡之前

Cluster Invoker

集群层的调用器,封装了容错策略(Failover、Failfast、Forking 等)。它负责把 Invocation 分发给具体的 provider,负载均衡是它内部选路的一环。

sticky(粘性)

会话粘性机制。如果启用了 sticky,同一个消费者对同一个服务的多次调用,会尽量固定到同一个 provider 上,用于有状态服务的场景。

active(活跃数)

当前在途请求数。RpcStatus 会记录每个 provider + method 维度的活跃调用计数,leastactive 策略用它来判断节点繁忙程度。

warmup(预热)

新上线 provider 的权重渐进机制。刚启动的节点不会立刻承担全量流量,而是逐步增加权重,避免冷启动时被瞬间打满。

weight(权重)

provider 的流量分配权重。权重高的节点会收到更多请求。可以在 URL 配置,也可以通过预热动态调整。

P2C(Power of Two Choices)

随机抽两个候选,比较后选更优的那个。adaptive 策略用的就是这个模式,避免了全表扫描,同时仍有一定随机性。

EWMA(Exponentially Weighted Moving Average)

指数加权移动平均,一种平滑历史数据的算法。公式:Vt = β × Vt-1 + (1 - β) × 新值adaptive 策略用它来平滑延迟估计。

reselect(重选)

兜底选路机制。当首选的 provider 不可用(比如在调用过程中被剔除),会重新从剩余候选里再选一个。


小结

Dubbo 的负载均衡表面看是「六选一」的配置项,背后其实是一组不同取舍的工程决策:

• 想要无脑兜底,用 random

• 想要平滑分布,用 roundrobin,但要接受它有状态、要清理缓存。

• 想要避开繁忙节点leastactive 简单,shortestresponse 更贴延迟。

• 想要key 亲和consistenthash,但别指望它感知负载——也别试图让它感知。

• 想要动态自适应adaptive 最全,但要保证 filter 和指标反馈链路是通的,否则会退化成不如简单策略的 P2C。

历史里最有价值的部分并非「哪个策略最好」,而是两条反复被验证的约束:别动已有策略的语义契约热路径成本就是 API 契约。这两条比任何选型建议都更值得带走。


本文基于 Dubbo 源码与 git 历史整理,commit 编号均可在官方仓库对应 PR 中查到。策略选型无银弹,生产环境建议按真实流量特征做对比验证。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-07-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 超级AI技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 概览
  • 一、调用链:负载均衡在哪一步生效
  • 二、六种策略的真实行为
    • 2.1 random:无状态的兜底
    • 2.2 roundrobin:平滑加权轮询
    • 2.3 leastactive:选活跃数最少的
    • 2.4 shortestresponse:滑动窗口估响应
    • 2.5 consistenthash:保 key 亲和
    • 2.6 adaptive:P2C + 多信号融合
  • 三、为什么没有独立的 ewma 策略
  • 四、consistenthash 的两次教训:别动语义契约
    • 4.1 加过载转移的尝试被回退
    • 4.2 正确性和热路径成本反复冒头
  • 五、从历史里能抽出的几条工作观察
  • 名词解释
    • SPI(Service Provider Interface)
    • Invoker
    • Directory
    • Router
    • Cluster Invoker
    • sticky(粘性)
    • active(活跃数)
    • warmup(预热)
    • weight(权重)
    • P2C(Power of Two Choices)
    • EWMA(Exponentially Weighted Moving Average)
    • reselect(重选)
  • 小结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档