分布式锁需要公平性吗?
简化版
是否需要公平锁取决于业务是否要求有界等待,而不是默认越公平越好。FIFO 公平能降低饥饿,但要维护等待队列并处理取消与失联,通常增加延迟和降低吞吐;多数短临界区用非公平竞争加退避即可。
详细版
先定义公平范围:同一锁 key 内按到达顺序、按优先级,还是只保证最终不会饿死。严格全局 FIFO 在分布式环境难以定义,因为客户端时钟和网络到达顺序不一致;工程上通常以锁服务确认入队的顺序为准。
公平实现可用 ZooKeeper 临时顺序节点或服务端等待队列,让队首获得锁并只唤醒后继者。需要处理超时取消、会话失效、队首卡住与惊群;若业务更重视吞吐,则允许刚释放时本地或新请求抢占,并通过随机退避和最大等待保护长尾。
完整版教学
一、公平到底指什么
公平不是一个布尔值。严格 FIFO 要求按权威入队序号授予;有界等待只要求请求在有限轮次内获得机会;优先级公平还允许高优任务插队。面试中先声明语义,否则“公平锁”可能被误解为按客户端发起时间排序。
记忆钩子:分布式系统没有天然的同时到达,公平顺序必须由某个权威观察点定义。
二、非公平锁为什么吞吐更高
锁释放瞬间,当前连接或刚到的请求可能直接抢到锁,省去持久化队列、唤醒下一位和上下文切换。临界区很短且请求同质时,这种 barging 往往提高吞吐。但持续有新请求时,网络慢或退避不巧的旧请求可能长期失败。
非公平:release -> 所有竞争者尝试 -> 一个成功
公平: enqueue(seq) -> 只通知 seq 最小者 -> grant
三、公平队列怎样实现
ZooKeeper 可为每个请求创建临时顺序节点,只监听前一个节点;前驱删除后自己成为最小序号才获得锁。自研锁服务也可在共识状态机中维护队列。临时/租约身份保证客户端失联后节点可清理,取消操作必须幂等并触发后继检查。
| 策略 | 顺序保证 | 吞吐 | 主要风险 |
|---|---|---|---|
| 自旋抢占 | 无 | 高 | 饥饿、惊群 |
| 随机退避 | 概率公平 | 较高 | 长尾不确定 |
| FIFO 队列 | 服务端到达序 | 中 | 队列维护 |
| 优先级队列 | 按等级 | 中 | 低优饥饿 |
四、公平的成本如何量化
假设临界区 10ms,纯串行理论上限约 100 QPS。公平授予若额外需要 2ms 持久化与通知,每轮变为 12ms,上限约 83 QPS,下降约 17%。真实成本还包括取消、会话检测和跨地域 RTT,因此不能只凭理念选择。
等待质量可看 P50/P99、最大等待、超时率和同一请求重试轮数。平均等待低但最大值达 30 秒,说明可能存在饥饿。
五、哪些场景值得公平
配额稀缺且每个租户都应获得机会、人工作业必须按提交顺序、锁等待直接体现用户排队时,公平性有业务价值。缓存刷新、短时去重或高吞吐内部任务通常不需要严格 FIFO。若任务耗时差异大,FIFO 还会产生队头阻塞,短任务被长任务拖住。
优先级场景应配老化机制,例如每等待 5 秒提升一级,避免低优请求永久饥饿。公平范围最好限定在单 key 或单租户,避免构建昂贵的全局队列。
六、超时取消和故障处理
客户端超时后必须取消队列节点,否则“幽灵队首”获锁却无人工作。取消与授予可能并发,需要由状态机决定唯一结果;客户端收到超时但服务端实际已授予时,应立即安全释放。会话断开不能立刻判死亡,要结合租约期权衡误判与恢复速度。
监控队列长度、最老等待年龄、授予序号跳跃、取消延迟和各租户等待分布。仅看获取成功率无法发现某一租户持续饥饿。
公平队列还要限制单个调用方的排队名额。否则恶意或故障客户端一次提交大量等待项,即使严格 FIFO,也会让后续正常请求长期排在其后。
多租户系统可先按租户做加权公平,再在租户内部 FIFO;这属于调度策略,不能简单等同于一条全局顺序队列。
七、常见误区与追问
- 误区:公平锁一定更高级。 它用吞吐和复杂度换等待可预测性。
- 误区:按客户端时间戳就是严格公平。 时钟不一致且消息到达顺序不同。
- 误区:FIFO 不会有长尾。 队首慢任务会造成 head-of-line blocking。
- 误区:客户端超时后等待节点会自动消失。 必须依赖可靠取消或会话清理。
- 追问:如何避免低优任务饿死? 使用等待老化、保底配额或最长等待上限。
- 追问:ZooKeeper 为什么只监听前驱? 避免节点删除时唤醒全部等待者形成惊群。
- 追问:怎样证明需要公平? 给出有界等待或业务顺序 SLO,而不是只说更合理。
八、加强记忆
回答公平锁先定义顺序的权威观察点,再比较吞吐、饥饿和队头阻塞。严格 FIFO 用顺序队列与定向唤醒实现,同时处理取消和会话失效;没有明确有界等待需求时,非公平锁配随机退避通常更经济。最终用等待分布而非平均成功率验证公平效果。