ZooKeeper 临时顺序节点如何实现分布式锁?
简化版
客户端在锁目录下创建临时顺序节点,序号最小者获得锁;其他客户端只监听自己的直接前驱,前驱删除后重新判断。临时节点随会话失效自动清理,顺序节点提供排队顺序,监听前驱可避免所有等待者同时被唤醒。
详细版
创建 /locks/order-123/lock- 的 EPHEMERAL_SEQUENTIAL 节点后,读取并排序子节点;自己最小则进入临界区,否则对直接前驱执行一次带 Watch 的存在性检查:返回存在才等待,返回不存在就立即重新排序,从而避免检查与监听之间丢事件。完成后删除自己的节点;断连时不能立刻认为失锁,只有会话过期才会删除节点。
方案具有公平排队和故障自动清理,但要处理 Watch 一次性、连接重建、会话过期、羊群效应、取消以及旧持有者恢复。节点序号可作为代次线索,但真正的 Fencing Token 必须传到资源侧原子校验。
完整版教学
一、三种机制各负责什么
临时节点把生命周期绑定到会话,会话确认过期后自动删除;顺序节点由 ZooKeeper 在父目录下追加单调序号;Watcher 提供状态变化通知。三者组合实现自动回收、排队顺序和高效唤醒,而不是依赖客户端轮询抢同一个节点。
记忆钩子:临时管回收,顺序管排队,前驱 Watch 管定向唤醒。
二、完整获取流程
1. create /locks/k/lock- EPHEMERAL_SEQUENTIAL -> lock-0007
2. getChildren(/locks/k) and sort
3. if self is smallest: acquired
4. else watch direct predecessor lock-0006
5. predecessor deleted -> repeat step 2
每次通知后必须重新读取和判断,因为 Watch 只说明发生变化,不保证自己已经成为最小节点。
三、为什么只监听直接前驱
若所有等待者都监听最小节点,持有者释放时会唤醒几百个客户端,它们同时读取子节点并竞争,形成羊群效应。每个节点只监听前驱时,一次释放通常只唤醒下一个候选者,读压力与并发切换更平滑。
| 监听方式 | 一次释放唤醒数 | 结果 |
|---|---|---|
| 都监听最小节点 | N-1 | 羊群、读放大 |
| 监听直接前驱 | 约 1 | 定向交接 |
| 固定轮询 | 取决于间隔 | 延迟或高 QPS |
四、如何避免丢失删除事件
错误流程是先确认前驱存在,再单独注册 Watch;前驱可能恰在两步之间删除,客户端将永久等待。ZooKeeper 的存在检查可同时设置 Watch;注册返回前若节点已不存在,就立刻重新排序。客户端库还要处理 Watch 一次性触发和重连后重新注册。
watchExists(predecessor)
EXISTS -> wait for NodeDeleted
MISSING -> list children again immediately
五、断连与会话过期不是一回事
连接暂时断开时,服务端会话和临时节点可能仍存在,客户端不能假定自己已失锁,也不能安全继续进行新的不可逆写,因为无法确认会话状态。重连后若仍是同一会话可重新检查;收到 Expired 后旧节点必将清理,客户端必须创建新会话、新节点并按新序号排队。
例如 sessionTimeout=20 秒,网络断开 5 秒不一定释放锁;若超过服务端确认的会话超时,接管延迟至少包含这段窗口。
六、公平性、取消与栅栏
顺序节点提供以 ZooKeeper 创建顺序为准的近似 FIFO,不代表按客户端本地发起时间。等待超时或取消时要删除自己的节点;删除与前驱通知并发时,重新判断自己是否已获锁,再决定安全释放。
节点后缀序号可作为 fencing token 的来源,但它只在特定父节点的计数空间内有序,而且顺序计数存在回绕边界,不能直接当成永久全局序号。方案必须固定比较范围、规划回绕/迁移,并由数据库或存储持久化最大代次;仅在应用层判断最小节点仍挡不住失去会话的旧客户端继续写。
七、常见误区与追问
- 误区:所有等待者都 Watch 当前持有者最简单。 会在释放时造成羊群效应。
- 误区:收到前驱删除事件就一定获得锁。 仍需重新读取并判断最小节点。
- 误区:TCP 断开就代表临时节点已删除。 节点要等会话被确认过期。
- 误区:会话重连后可以沿用旧锁句柄。 Expired 后必须重新创建并排队。
- 追问:怎样避免检查与监听之间丢事件? 使用带 Watch 的存在检查,缺失则立即重查。
- 追问:为什么比单节点抢锁更公平? 服务端顺序号显式记录权威入队顺序。
- 追问:旧客户端恢复怎么办? 最终资源用 fencing token 拒绝旧序号写入。
八、加强记忆
ZooKeeper 锁按“建临时顺序节点—最小者持锁—监听直接前驱—事件后重查—会话过期清理”记忆。正确性难点在 Watch 丢失窗口、断连与 Expired 的区别以及旧 owner 恢复;公平排队和自动清理之外,关键数据写仍应让资源侧校验单调代次。