← 返回题目列表

分布式锁抢锁失败后如何退避重试?

中等 第 21 / 26 题 更新于 2026/10/01
分布式锁退避重试抖动重试风暴
学习 AI 实战项目

简化版

抢锁失败后应在请求总截止时间内做有界指数退避并加入随机抖动,避免所有竞争者按固定周期同时重试。能由锁服务定向通知时优先等待通知;超过预算就快速失败、排队或降级,不能无限占用业务线程。

详细版

区分“锁被占用”和“锁服务错误”:前者按竞争策略等待,后者受重试预算与熔断控制。Full Jitter 可从 0..min(cap, base×2^attempt) 随机取等待时间,每次重试前检查 deadline 和取消信号;释放通知也要配超时轮询,防止通知丢失。

参数从持锁 P99、上游延迟预算和最大并发反推。监控每次请求尝试数、总等待、最终结果、重试 QPS 与 top key;热点长期输入大于单 key 服务能力时,应排队、合并或重构资源模型,而不是继续加快重试。

完整版教学

一、立即重试为什么危险

一百个客户端同时失败后立即循环,会持续轰击锁服务;锁释放瞬间又同时抢占,只有一个成功,其余继续制造流量。业务线程、连接池和锁后端可能先被重试耗尽,这就是典型 thundering herd。

心法:重试不是消除失败,而是用时间换取下一次成功机会;没有预算的重试就是放大器。

二、指数退避与抖动

第 n 次上限可取 min(cap, base×2^n),Full Jitter 再在 0 到上限间随机。随机化打散客户端节奏,cap 防止等待无限增长。例如 base=50ms、cap=2s,第 0~3 次上限为 50、100、200、400ms。

delay_n = random(0, min(2000ms, 50ms * 2^n))
if now + delay_n >= deadline: stop

三、轮询与通知怎么选

轮询实现简单但有空请求;发布/订阅或前驱监听能定向唤醒,却要处理通知先于订阅、连接中断和消息丢失。稳妥实现通常“通知 + 最长等待超时后复查”,通知只是加速器,锁状态仍是事实来源。

策略负载唤醒速度风险
忙轮询高快风暴
固定间隔中中同步碰撞
抖动退避低可控参数选择
定向通知低快丢通知/连接状态

四、总截止时间比次数更重要

上游只剩 300ms 时,即使“最多重试 5 次”也没有意义。每次获取、等待与业务执行共享同一个 deadline,并预留临界区执行时间。取消或客户端断开后立即停止排队,避免幽灵请求随后获锁。

例如请求预算 1 秒、业务 P99 400ms,则锁等待最多约 600ms,还要扣除网络余量;不能把 1 秒全部花在抢锁上。

五、不同失败原因不同处理

明确返回 BUSY 才进行竞争退避;认证失败、非法 key 不重试;锁服务超时属于结果未知,应查询或等待,不能假定未获得后直接再建一把锁。后端大面积故障时使用熔断和全局重试预算,避免每个请求独立重试三次。

业务允许时可返回“处理中”或投递队列,让消费者按 key 串行,避免 HTTP 线程同步等待。

六、容量与观测

临界区平均 25ms 时单 key 理论能力约 40 QPS;输入 100 QPS 无论怎样退避都无法长期稳定。退避只能平滑瞬时竞争,不能创造串行资源容量。此时要削峰、合并操作、拆分冲突域或拒绝过量请求。

监控 attempt 分布、总等待 P99、放弃率、重试/原始流量比、等待者数量与 top key,并在 Trace 中显示每轮原因和延迟。

重试预算应在调用链上共享:上游已经重试一次时,下游不能再独立放大为三次,否则三层各重试三次可能形成最多 27 次尝试。

服务端还可返回建议等待时间,但客户端仍需加入抖动并受自身 deadline 限制,避免所有请求完全遵循同一个时间点再次唤醒。

七、常见误区与追问

  • 误区:重试越快成功越快。 高频重试会扩大争用并拖慢持有者。
  • 误区:固定间隔已经能降压。 同时开始的客户端仍会同步碰撞。
  • 误区:限制重试次数就够了。 还要受统一 deadline 和全局预算约束。
  • 误区:收到通知就一定能获得锁。 通知只是提示,仍需原子竞争。
  • 追问:为什么推荐 Full Jitter? 它比确定性间隔更能打散群体重试。
  • 追问:通知丢失怎么办? 设置等待超时后重新检查权威锁状态。
  • 追问:热点持续过载怎么办? 排队限流或改变资源模型,退避不能修复容量缺口。

八、加强记忆

退避重试按“分原因、有抖动、受 deadline、可取消、看容量”设计。锁忙用指数抖动或定向通知,参数错误不重试,未知结果先确认;当输入长期超过单 key 串行能力时,必须排队或拒绝,而不是靠更多重试掩盖过载。