Zookeeper分布式锁原理

发布于:2025-09-05 ⋅ 阅读:(22) ⋅ 点赞:(0)

核心基础

在理解原理前,先回顾三个 ZooKeeper 的核心特性:

  1. 临时节点(Ephemeral Nodes):创建该节点的客户端会话(Session)一旦失效(如断开连接),那么这个节点会被自动删除。这保证了即使客户端崩溃,锁也能被自动释放,避免了死锁。
  2. 顺序节点(Sequential Nodes):创建节点时,ZooKeeper 会自动在节点名称后附加一个单调递增的序列号(如 lock-0000000001)。这保证了所有节点的创建顺序是全局唯一的。
  3. Watcher 机制:客户端可以在某个节点上设置监听(Watcher)。当该节点被删除、修改等事件发生时,ZooKeeper 会通知所有设置了 Watcher 的客户端。这是实现客户端间高效协调和通知的关键。

分布式锁的实现流程(以排他锁为例)

假设我们有一个锁的根节点 /locks/my_lock

1. 获取锁(Acquire Lock)
  • 步骤一:创建临时顺序节点
    所有想要获取锁的客户端,都会在 /locks/my_lock 下创建一个临时顺序节点,例如:

    • Client A 创建了 /locks/my_lock/lock-0000000001
    • Client B 创建了 /locks/my_lock/lock-0000000002
    • Client C 创建了 /locks/my_lock/lock-0000000003
  • 步骤二:检查自己是否是最小节点
    每个客户端都会获取 /locks/my_lock 下的所有子节点,并按序列号排序。

    • 对于 Client A:它发现自己的节点 lock-0000000001 是序列号最小的节点。那么它就成功获得了锁,可以执行自己的业务逻辑。
    • 对于 Client B:它发现自己的节点 lock-0000000002 不是最小的,前面还有 lock-0000000001。这意味着锁正被 Client A 持有。
    • 对于 Client C:同样,它发现自己不是最小的,锁不可用。
  • 步骤三:监听前一个节点(等待锁)
    Client B 和 Client C 不会不停地轮询询问“轮到我了吗?”。而是采用更高效的方式:

    • Client B:它会向它前面的那个节点(即 lock-0000000001)设置一个 Watcher 监听
    • Client C:它会向它前面的那个节点(即 lock-0000000002)设置一个 Watcher 监听

    这样,每个客户端都只监听它前面的节点,形成一个等待队列。

整个过程如下图所示,它清晰地展示了客户端如何通过创建节点、排序和监听来形成一个有序的等待队列:

请添加图片描述

2. 释放锁(Release Lock)
  • 情况一:正常释放
    Client A 完成业务逻辑后,主动删除它创建的那个临时节点 lock-0000000001

  • 情况二:异常释放
    如果 Client A 在持有锁期间进程崩溃或网络断开,由于它创建的是临时节点,ZooKeeper 会自动检测到会话失效,并自动删除 lock-0000000001

3. 唤醒下一个等待者(Notify Next)
  • lock-0000000001 被删除(无论是主动还是被动)时,ZooKeeper 会通知所有监听了这个节点的客户端。也就是 Client B 会收到一个通知
  • Client B 被唤醒后,它会重复“步骤二”:再次获取 /locks/my_lock 下的所有子节点。
    • 此时子节点是 [lock-0000000002, lock-0000000003]
    • Client B 发现自己的节点 lock-0000000002 现在是最小的了!于是它成功获得了锁。
  • 同理,当 Client B 释放锁,删除 lock-0000000002 后,Client C 会被唤醒,进而获得锁。

这种设计的精妙之处

  1. 避免羊群效应(Herd Effect)
    传统的做法是所有客户端都监听同一个锁节点,释放时所有客户端都被唤醒并同时争抢,给 ZooKeeper 和服务端带来巨大压力。而只监听前一个节点的方式,每次锁释放都只精确地唤醒一个客户端(队列中的下一个),压力非常小。

  2. 天然的公平锁(Fair Lock)
    由于节点顺序是全局唯一的,并且严格按照顺序唤醒,所以每个客户端获取锁的顺序就是它们创建节点的顺序,实现了先来后到的公平性。

  3. 自动防死锁(Deadlock-Free)
    得益于临时节点的特性,任何客户端持有的锁都会在会话结束时自动释放,无需担心因为客户端宕机而导致的锁永久死锁问题。

  4. 可重入性(Reentrancy)(需要客户端实现):
    如果同一个客户端线程想再次获取锁,可以在客户端内存中记录一个计数器(count)。检查锁的持有者时,不仅要看节点序列号,还要看节点名称中是否包含自己的客户端ID。如果是自己持有的,就直接增加计数器并返回成功,而无需再创建节点。


总结:ZooKeeper 分布式锁的核心步骤

步骤 操作 说明
1. 争抢 所有客户端在锁目录下创建临时顺序节点 宣告自己的排队资格。
2. 判断 获取锁目录下所有子节点,判断自己是否是序号最小的节点。 是则成功获锁;否则继续。
3. 等待 如果不是最小,则监听自己前面那个节点的删除事件。 避免羊群效应,高效等待。
4. 释放 业务处理完,主动删除自己创建的那个节点。 正常释放锁。
5. 唤醒 节点被删除,ZooKeeper 通知下一个监听它的客户端 下一个客户端被唤醒,回到步骤2。

在实际开发中,我们通常不直接使用 ZooKeeper 的原生 API 来实现锁,而是使用更高级的封装库,例如 Apache Curator。Curator 提供了一个成熟、稳健的分布式锁实现(InterProcessMutex),直接开箱即用,避免了手动处理各种极端情况(例如连接丢失、重试等)。但理解其底层原理对于诊断问题和设计系统至关重要。


网站公告

今日签到

点亮在社区的每一天
去签到