实现原理
Zookeeper 分布式锁
一、实现原理
1.1 临时节点方案

临时节点方案的原理如下:
- 让多个进程(或线程)竞争性地去创建同一个临时节点,由于
ZooKeeper 不允许存在两个完全相同节点,因此必然只有一个进程能够抢先创建成功 ; - 假设是进程
A 成功创建了节点,则它获得该分布式锁。此时其他进程需要在parent_node 上注册监听,监听其下所有子节点的变化,并挂起当前线程; - 当
parent_node 下有子节点发生变化时候,它会通知所有在其上注册了监听的进程。这些进程需要判断是否是对应的锁节点上的删除事件。如果是,则让挂起的线程继续执行,并尝试再次获取锁。
这里之所以使用临时节点是为了避免死锁:进程
临时节点方案的实现比较简单,但是其缺点也比较明显:
- 缺点一:当
parent_node 下其他锁变动或者被删除时,进程B ,C,D 也会收到通知,但是显然它们并不关心其他锁的释放情况。如果parent_node 下存在大量的锁,并且程序处于高并发状态下,则ZooKeeper 集群就需要频繁地通知客户端,这会带来大量的网络开销; - 缺点二:采用临时节点方案创建的锁是非公平的,也就是说在进程
A 释放锁后,进程B ,C,D 发起重试的顺序与其收到通知的时间有关,而与其第一次尝试获取锁的时间无关,即与等待时间的长短无关。
当程序并发量不高时,可以采用该方案来实现,因为其实现比较简单。而如果程序并发量很高,则需要采用下面的临时有序节点方案:
1.2 临时有序节点方案

采用临时有序节点时,对应的流程如下:
- 每个进程(或线程)都会尝试在
parent_node 下创建临时有序节点,根据临时有序节点的特性,所有的进程都会创建成功; - 然后每个进程需要获取当前
parent_node 下该锁的所有临时节点的信息,并判断自己是否是最小的一个节点,如果是,则代表获得该锁; - 如果不是,则挂起当前线程。并对其前一个节点注册监听(这里可以通过
exists 方法传入需要触发Watch 事件) ; - 如上图所示,当进程
A 处理完成后,会触发进程B 注册的Watch 事件,此时进程B 就知道自己获得了锁,从而可以将挂起的线程继续,并开始业务的处理。
这里需要注意的是一种特殊的情况,其过程如下:
- 如果进程
B 创建了临时节点,并且通过比较后知道自己不是最小的一个节点,但还没有注册监听; - 而
A 进程此时恰好处理完成并删除了01 节点; - 接着进程
B 再调用exist 方法注册监听就会抛出IllegalArgumentException 异常。这虽然是一个异常,通常代表前一个节点已经不存在了。
在这种情况下进程
通过上面对的介绍,可以看出来临时有序节点方案正好解决了临时节点方案的两个缺点:
- 每个临时有序节点只需要关心它的上一个节点,而不需要关心其他的额外节点和额外事件;
- 实现的锁是公平的,先到达的进程创建的临时有序节点的值越小,因此能更快地获得锁。
临时有序节点方案的另外一个优点是其能够实现共享锁,比如读写锁中的读锁。
1.3 读写锁
如下图所示,可以将临时有序节点分为读锁节点和写锁节点:
- 对于读锁节点而言,其只需要关心前一个写锁节点的释放。如果前一个写锁释放了,则多个读锁节点对应的线程可以并发地读取数据;
- 对于写锁节点而言,其只需要关心前一个节点的释放,而不需要关心前一个节点是写锁节点还是读锁节点。因为为了保证有序性,写操作必须要等待前面的读操作或者写操作执行完成。
