前一篇保证了 Directory 里的地址大体正确。真正决定「这次调用打哪台、失败了怎么办」的,是 Dubbo 最有辨识度的一层——Cluster。
本篇把 Cluster 四件套拆开:Directory 提供候选人,Router 按规则裁剪,LoadBalance 做选择,Cluster 策略决定失败后的行为。配错这一层,轻则流量不均,重则把一次抖动放大成雪崩。
本文是 Dubbo 深挖系列的第 3 篇。 ① 架构开篇 → ② 注册发现 → ③ Cluster(本篇) → ④ 协议 Triple → ⑤ 流量治理。
一句话定位:Cluster 层 = 在可用 Invoker 集合上做「过滤 → 选择 → 容错」;负载均衡只是其中一环,重试策略才是故障放大器的开关。
TL;DR
- 调用顺序:
ClusterInvoker→ 取 Directory 列表 → Router 链过滤 → LoadBalance 选择 → 调用;失败则按 Cluster 策略决策。 - 容错策略:Failover(默认重试)、Failfast、Failsafe、Failback、Forking、Broadcast…——写路径慎用 Failover。
- 负载均衡:Random(可加权)、RoundRobin、LeastActive、ConsistentHash 等;「均匀」永远受权重与路由约束。
- 路由:条件路由、标签路由、脚本路由——灰度与单元化的主杠杆。
- 粘滞连接(Sticky):同会话打同一节点,省连接但损均匀、放大单点故障面。
- AdTech:扣预算等非幂等写 →
cluster=failfast或retries=0;读画像可有限 Failover + 总超时预算。
Table of contents
Open Table of contents
1. Cluster 四件套
| 组件 | 职责 |
|---|---|
| Directory | 持有当前可用 Invoker 列表(随注册通知更新) |
| Router | 按规则过滤/排序列表(机房、标签、方法、参数) |
| LoadBalance | 在过滤后的列表里选一个 |
| Cluster | 组织一次「逻辑调用」:是否重试、是否并行、是否吞掉异常 |
可以把它想成:Directory 是菜单,Router 是忌口,LoadBalance 是点菜,Cluster 是「这道菜糊了是否换一家重做」。
2. 容错策略:失败之后怎么办
| 策略 | 行为 | 适用 |
|---|---|---|
| Failover | 失败换其他实例重试(默认常带 retries) | 幂等读;写路径危险 |
| Failfast | 一次失败立即抛错 | 非幂等写、要快速失败 |
| Failsafe | 失败吞掉,返回空/默认 | 审计、日志类「失败可忽略」 |
| Failback | 失败后台定时重试 | 消息类、最终补偿 |
| Forking | 并行调多个,一个成功即返回 | 对延迟极度敏感的读,成本高 |
| Broadcast | 逐个调用全部,任一下失败则失败 | 通知刷新缓存等 |
2.1 为什么默认 Failover 会害死人
Failover 的隐藏乘数:
下游超时 ≈ 100ms,retries=2 → 调用方可能等 300ms+,且故障时 QPS 近似 ×(1+retries)。
竞价链路总 deadline 若只有 150–200ms,一次 Failover 就能吃光盘,并拖垮更多线程。这与 RPC 开篇 的「无脑重试放大」同一故事。
铁律:
- 非幂等写:
retries=0或failfast,业务层用幂等键保证「可重」时再谈重试; - 幂等读:可小额重试,但必须有端到端超时预算;
- 重试要区分错误类型:连接失败可换节点;业务异常(余额不足)重试无意义。
3. 负载均衡
3.1 Random(加权随机)
默认常见选择。权重高的实例命中概率高;实现简单、分散性好。权重来自注册元数据或动态配置——发布时把新版本权重从 0 爬升,是一种朴素金丝雀。
3.2 RoundRobin(加权轮询)
更平滑的序列感;在权重变更、列表频繁变动时要实现正确的加权轮询,边界比随机多。
3.3 LeastActive(最少活跃)
优先选「正在处理中的请求更少」的实例——对快慢不均的集群更公平。注意活跃数统计的是客户端视角的 in-flight,不是 Provider CPU;慢节点会自然少接新请求。
3.4 ConsistentHash(一致性哈希)
按参数(如 userId)把请求粘到同一节点。适合缓存亲和、会话亲和;节点增减会扰动映射(虚拟节点缓解但不消除)。别把一致性哈希当成「顺序保证」——那是业务语义,不是 LB 承诺。
3.5 均匀性的幻觉
「不均」常见原因:
- 路由后只剩少数候选;
- 权重差异大;
- 粘滞连接;
- 部分实例刚上线、预热未完成;
- 客户端少、样本不足(小流量看随机本就不平)。
4. 路由:灰度与单元化的主杠杆
路由发生在 LB 之前:先裁剪,再选择。
4.1 条件路由(Condition)
形如:
method=deduct => region=unit-a
consumer.application=bidder => tag=gray
用消费者属性、方法名、附件参数匹配,收窄 Provider 集合。匹配过严会导致过滤后为空——表现仍是 No provider,但根因在规则而非注册。
4.2 标签路由(Tag)
给 Provider 打 tag=gray,Consumer 带同一 tag 优先/强制命中。适合金丝雀与平行试验流量。记住:无 tag 的存量流量怎么回落(是否允许打到无 tag 节点)必须显式规定,否则灰度节点会被「不相关流量」冲垮,或灰度流量掉进基线。
4.3 脚本路由
灵活但危险——脚本缺陷可以瞬间把流量引到错误集合。生产环境应平台托管、评审、可快速回滚。
5. 粘滞、分区与可用区亲和
- Sticky:同一客户端(或同会话)尽量固定 Invoker,减少建连;节点挂了要能自动切换。
- 同机房优先:用路由或 Prefer 策略降低跨区 RTT——广告竞价里几毫秒都值钱。
- 分区容灾:本单元无候选时是否跨单元——「严格单元化」与「降级跨单元」是产品决策,要写进规则,别靠默认。
6. 与超时、线程池的耦合
Cluster 策略不是孤立旋钮:
| 旋钮 | 与 Cluster 的互动 |
|---|---|
| timeout | Failover 总耗时 ≈ 单次超时 × 尝试次数 |
| retries | 直接决定放大倍数 |
| actives / 连接限制 | 限制单机压力,和 LeastActive 互补 |
| 线程池 | Provider 打满时,换节点重试可能「毒打」整个集群 |
经验:先定 deadline,再反推单次 timeout 与最大尝试次数,最后才选 loadbalance 名字。
7. AdTech 配置样板
扣预算(写,非幂等)
cluster=failfast
retries=0
timeout=50
loadbalance=leastactive
拉用户画像(读,可幂等)
cluster=failover
retries=1
timeout=40
loadbalance=random
并配合:业务幂等键、单元路由、发布窗口关闭长重试。
8. 生产反模式
- 全局默认
retries=2复制到所有接口。 - 灰度规则写完不测「无匹配」路径,上线即空列表。
- 用 Forking 抗延迟却忘了 Provider 四倍负载。
- 把一致性哈希当分库分表主键策略,节点伸缩后大面积漂移仍一脸震惊。
- 只看 LB 名称,不看路由过滤后的候选集大小。
9. 常见误解 ↔ 正解
| 常见误解 | 正解 |
|---|---|
| Failover 更「高可用」 | 对非幂等写,它更「高危」 |
| Random 一定不均匀 | 大样本下可接受;不均先查权重/路由 |
| 路由是运维可选项 | 单元化/灰度下它是正确性的一部分 |
| Cluster 能掩盖错误地址 | 列表全是僵尸时换谁都超时 |
| 最少活跃=最空闲 CPU | 是客户端 in-flight,不是主机监控 |
10. 速查表
- 流水线:Directory → Router → LoadBalance → Cluster 策略。
- 写:Failfast / retries=0;读:有限 Failover + 预算。
- LB:Random / RoundRobin / LeastActive / ConsistentHash。
- 路由在 LB 前;过滤为空要当一级故障。
- 总耗时:timeout × 尝试次数 ≤ 上游 deadline。
下一篇钻到字节层——协议栈与 Triple / 序列化:经典 Dubbo 协议与基于 HTTP/2 的 Triple 如何承载一次 Invocation。
延伸阅读
本系列内部串读:
相关:
一手资料:
- Apache Dubbo. Cluster:容错与路由官方说明。
- Apache Dubbo. Load Balance:负载均衡策略。