负载均衡与容错机制
1. 这是什么
很多人刚学 Dubbo 时,会把“负载均衡”和“容错”混成一件事。
其实它们解决的是两个不同层面的问题:
- 负载均衡:这次请求应该打到哪个 Provider
- 容错机制:如果这次调用失败了,接下来怎么办
你可以先这样理解:
负载均衡决定“选谁来处理”,容错机制决定“处理失败后怎么办”。
比如现在有 3 台用户服务:
- 10.0.0.11
- 10.0.0.12
- 10.0.0.13
Consumer 发起一次请求时,首先要回答:
- 这次选哪一台?
这就是负载均衡。
如果刚好选中的那台超时了,还要继续回答:
- 是直接报错?
- 还是换一台重试?
- 还是快速失败?
- 还是返回默认值?
这就是容错机制。
2. 为什么重要
远程调用和本地方法调用最大的区别之一是:
- 本地调用失败的变量少
- 远程调用失败的变量很多
例如:
- 某台机器负载飙高
- 网络抖动
- 服务实例重启
- 单个节点 GC 卡顿
- 某个机房延迟变大
如果没有合理的负载均衡和容错策略,就会出现:
- 流量总压到少数节点
- 故障节点持续被打爆
- 一次超时引发级联重试
- 写请求被重复执行
- 整条链路被拖慢
所以这一章的本质,不只是讲几个策略名词,而是在回答:
分布式系统里,怎么把“偶发失败”控制在可接受范围内。
3. 先建立最核心的直觉
可以把 Dubbo 调用看成“打车”:
负载均衡像什么
像你在平台上打车,系统要从附近司机里挑一个:
- 随机挑
- 轮流分配
- 优先给距离近的
- 尽量让同一个乘客总是分到同一类司机
容错像什么
像你叫的车如果取消了,你要怎么处理:
- 重新叫一辆
- 立刻放弃
- 换别的平台
- 返回一个兜底方案
所以你会发现:
- 负载均衡发生在“调用前”
- 容错策略发生在“调用失败后”
这两个动作顺序不同,作用也不同。
4. 常见负载均衡策略
4.1 Random:随机
随机是最常见、也最容易理解的一种。
特点:
- 从多个 Provider 中随机选一个
- 实现简单
- 在实例数量足够多、请求量足够大时,整体分布通常比较均匀
适合:
- 大多数普通读请求
- 节点性能比较接近的场景
通俗理解:
- 每次从可用实例里随机抽一个
优点
- 简单
- 分布通常比较自然
- 不容易因为顺序固定导致局部热点
缺点
- 单次短时间窗口内可能不均匀
- 不考虑实例当前真实负载
4.2 Round Robin:轮询
轮询的思路是:
- 这次给 A
- 下次给 B
- 再下次给 C
- 然后再回到 A
适合:
- 节点性能相近
- 请求耗时也比较接近
优点
- 直观
- 理论上分配比较平均
缺点
如果某一台机器虽然“轮到它了”,但它刚好很慢,轮询并不会自动避开它。
所以轮询的问题在于:
- 它只关注“分配顺序”
- 不关注“实例当前状态”
4.3 Least Active:最少活跃调用数
这个策略更关注当前实例是否忙。
它会优先选择:
- 当前正在处理请求数更少的实例
通俗理解:
- 谁现在手头活少,就先把新请求给谁
适合:
- 各实例处理能力不同
- 请求耗时差异较大
优点
- 比纯随机、纯轮询更能反映当前负载情况
缺点
- 实现和理解都稍复杂
- 对统计数据准确性有依赖
4.4 Consistent Hash:一致性哈希
一致性哈希常见于“同一类请求尽量打到同一台机器”的场景。
例如:
- 同一个用户 ID 的请求尽量落到同一实例
- 同一个会话尽量命中同一机器上的缓存
通俗理解:
- 不是随机选机器
- 而是根据某个 key 算出“它更适合去哪台机器”
适合:
- 有会话粘性需求
- 希望同类请求稳定命中同一节点
- 本地缓存命中率很重要
优点
- 同一个 key 通常映射到固定实例
- 有利于局部缓存命中
缺点
- 节点变更时,映射关系会变化
- 不适合所有业务都无脑使用
5. 负载均衡到底怎么选
没有“永远最好的”策略,只有“更适合当前业务”的策略。
可以先这么记:
- 普通均匀流量:随机很常见
- 追求平均分配:轮询可作为入门理解
- 实例忙闲差异明显:最少活跃更合适
- 希望同一类请求稳定落同一机器:一致性哈希更合适
不要把负载均衡理解成一道标准答案题,它更像权衡题。
6. 常见容错策略
容错机制解决的是:
- 调用失败后怎么处理
这件事一定要结合业务类型来定。
6.1 Failfast:快速失败
含义是:
- 调用失败后立刻报错
- 不做额外重试
适合:
- 非幂等写请求
- 对重复执行敏感的操作
例如:
- 扣库存
- 下单
- 支付确认
为什么适合写请求?
因为很多写操作不能随便重试,不然可能产生重复副作用。
6.2 Failover:失败转移
含义是:
- 调用某个 Provider 失败后
- 再换一个 Provider 继续重试
适合:
- 幂等读请求
- 查询类请求
例如:
- 查用户资料
- 查商品详情
- 查配置
优点
- 能提高读请求成功率
风险
- 会放大调用次数
- 在下游整体不稳定时,可能让雪崩更严重
所以要特别注意:
重试不是免费补救,它会扩大流量。
6.3 Failsafe:失败安全
含义通常是:
- 调用失败后,不抛出强错误
- 记录日志或忽略异常
适合:
- 非核心辅助操作
- 不影响主流程的统计、审计类动作
例如:
- 异步埋点
- 非关键日志上报
不适合:
- 核心交易流程
6.4 Failback:失败自动恢复
含义是:
- 失败后先记录下来
- 后续再后台重试补偿
适合:
- 允许最终一致
- 不要求请求必须同步成功
例如:
- 某些通知、消息补偿类场景
6.5 Forking / Broadcast(了解即可)
有些策略会:
- 并行调用多个实例,谁先返回用谁
- 或广播到所有实例
这些策略适用面更窄,通常只在特定场景下使用。
入门阶段先重点掌握:
- 快速失败
- 失败转移
就够用了。
7. 为什么写请求通常不适合盲目重试
这是实战里最重要的点之一。
假设你有一个创建订单接口:
createOrder(userId, productId)如果第一次调用已经在服务端执行成功,但 Consumer 因为网络超时没收到结果,这时如果再重试一次,可能发生:
- 第二次又创建出一个订单
- 或第二次扣减了一次库存
于是就出现了典型问题:
- 重复下单
- 重复扣款
- 重复发券
所以写请求一般要谨慎:
- 能不重试就别乱重试
- 真要重试,必须有幂等保障
这也是为什么:
读请求和写请求的容错策略,往往不能一刀切。
8. 动手验证:感受策略差异
如果你本地有 Dubbo 环境,可以做几个小实验。
8.1 准备两个 Provider
例如启动两个用户服务实例:
- provider-A
- provider-B
8.2 故意让一个实例变慢
在其中一个实例里加:
Thread.sleep(3000);8.3 连续发起请求
观察:
- 随机策略下,请求分布是否均匀
- 轮询策略下,是否会稳定轮到慢实例
- 最少活跃策略下,慢实例是否逐渐少接请求
8.4 再模拟失败
把其中一个实例停掉,观察:
- 快速失败时,Consumer 如何报错
- 失败转移时,是否会切到另一台实例继续调用
这类实验能让你真正理解:
- 负载均衡在选谁
- 容错策略在失败后怎么收场
9. 超时设置为什么和容错强相关
很多人只盯着“重试次数”,却忽略超时。
其实超时是容错里的关键参数。
比如:
- 单次超时 5 秒
- 重试 2 次
那一次最坏调用时间,可能就远超你想象。
这会导致:
- 上游线程堆积
- 接口整体变慢
- 故障在链路里层层放大
所以容错不是只看“要不要重试”,还要同时看:
- 超时多久
- 重试几次
- 总体 SLA 能否接受
10. 最容易踩的坑
10.1 所有接口统一重试
这是最危险的做法之一。
读写不分地统一重试,极容易把写接口搞出重复副作用。
10.2 看到失败就拼命重试
当下游已经不稳定时,盲目重试往往不是修复,而是放大故障。
10.3 只关注平均分配,不关注实例健康
负载均衡不是“分得平均”就够了,还要关注:
- 某台实例是不是已经慢了
- 某台实例是不是故障前兆
10.4 一致性哈希乱用
如果业务根本不需要“同类请求稳定命中同一实例”,那上来就用一致性哈希,只会增加理解和维护成本。
10.5 超时设置过大
超时过大会导致故障暴露太慢,线程和连接资源会被拖住。
11. 一套实用决策思路
以后你给某个 Dubbo 接口选策略时,可以先问自己 4 个问题:
- 这是读请求还是写请求?
- 它是不是幂等?
- 调用失败后,业务是更怕“失败”,还是更怕“重复执行”?
- 下游实例之间是否需要稳定路由到同一台?
通常可以得到一个比较务实的起点:
- 查询类接口:可考虑失败转移 + 合理超时
- 核心写接口:优先快速失败 + 业务幂等设计
- 本地缓存敏感接口:可评估一致性哈希
12. 练习建议
练习 1:为读写接口分别选策略
任选两个接口:
- 一个查询接口
- 一个写入接口
分别设计:
- 负载均衡方式
- 容错策略
- 超时设置
练习 2:模拟一次下游故障
步骤:
- 启动两个 Provider
- 停掉其中一个
- 观察不同容错策略的行为差异
练习 3:思考重试风险
找一个“创建/扣减/支付”类接口,分析:
- 如果 Consumer 因超时重试,会造成什么副作用
13. 自测问题
- 负载均衡和容错分别解决什么问题?
- 为什么写请求通常不适合盲目重试?
- 一致性哈希最适合哪类场景?
- 超时设置为什么会直接影响容错效果?
- 当下游已经抖动时,为什么重试可能会放大故障?
14. 这一章你至少要带走什么
如果你看完只记住 4 件事,就先记这 4 件:
- 负载均衡负责选实例,容错机制负责处理失败
- 读请求和写请求不能用同一套重试思路
- 重试会放大流量,必须谨慎设置
- 超时、负载策略、容错策略要一起看,不能孤立配置
把这几个点建立起来,后面你看 Dubbo 的集群容错配置时,就不会只停留在“背策略名”了。