如何用Zookeeper来实现分布式锁?
什么是zookeeper临时顺序节点?
例如 :
/
动物 植物
猫 仓鼠 荷花 松树
zookeeper的数据存储结构就像一棵树,这棵树由节点组成,这种节点叫做zonde.
# znode分为四种类型 :
1.持久节点(persistent)
默认的节点类型.创建节点的客户端与zookeeper断开连接后,该节点依旧存在.
2.持久节点顺序节点(persistent_sequential)
所谓顺序节点,就是在创建节点时,zookeeper根据创建的时间顺序给该节点名称进行编号.
例如 :
仓鼠
仓鼠0001 仓鼠0002 仓鼠0003
3.临时节点(ephemeral)
和持久节点相反,当创建节点的客户端与zookeeper断开连接后,临时节点会被删除 :
4.临时顺序节点(ephemeral_sequential)
临时顺序节点结合了临时节点和顺序节点的特点 : 在创建节点时,zookeeper根据创建的时间顺序给该节点名称进行编号 ; 当创建节点的客户端与zookeeper
断开连接后,临时节点会被删除.
# zookeeper分布式锁的原理
zookeeper分布式锁恰恰应用了临时顺序节点
## 获取锁 :
首先,在zookeeper当中创建一个持久节点parentlock.当第一个客户端想要获得锁时,需要在parentlock这个节点下面创建一个临时顺序节点lock1.
之后,client1查找parentlock下面所有的临时顺序节点并排序,判断自己所创建的节点lock1是不是顺序最靠前的一个.如果是第一个节点,则成功获得锁.
这时候,如果再有一个客户端client2前来获取锁,则在parentlock下再创建一个临时顺序节点lock2.
client2查找parentlock下面所有的临时顺序节点并排序,判断自己所创建的节点lock2是不是顺序最靠前的一个,结果发现节点lock2并不是最小的.
于是,client2向排序仅比它靠前的节点lock2注册watcher,用于监听lock1节点是否存在.这意味着client2抢锁失败,进入了等待状态.
这时候,如果又有一个客户端client3前来获取锁,则在parentlock下再创建一个临时顺序节点lock3.
client3查找parentlock下面所有的临时顺序节点并排序,判断自己所创建的节点lock3是不是顺序最靠前的一个,结果同样发现节点lock3并不是最小的.
于是,client3向排序仅比它靠前的节点lock2注册watcher,用于监听lock2节点是否存在.这意味着client3同样抢锁失败,进入了等待状态.
这样一来,client1得到了锁,client2监听了lock1,client3监听了lock2.这恰恰形成了一个等待队列,很像是java当中的reentrantlock所依赖的aqs(abstractqueuedsynchronizer)
## 释放锁 :
释放锁分为两种情况 :
1.任务完成,客户端显示释放
当任务完成时,client1会显示调用删除节点lock1的指令.
2.任务执行过程中,客户端崩溃
获得锁的client1在任务执行过程中,如果duang的一声崩溃则会断开与zookeeper服务端的链接.根据临时节点的特性,相关联的节点lock1会随之自动删除.
由于client2一直监听着lock1的存在状态,当lock1节点被删除,client2会立刻收到通知.这时候client2会再次查询parentlock下面的所有节点,确认自己创建
的节点lock2是不是目前最小的节点.如果是最小,则client2顺利成章获得锁.
同理,如果client2也因为任务完成或者节点崩溃而删除了节点lock2,那么client3就会接到通知.最终,client3成功得到了锁.
zookeeper和redis分布式锁的比较
zookeeper的
优点 :
1.有封装好的框架,容易实现.
2.有等待锁的队列,大大提升抢锁效率.
缺点 :
1.添加和删除节点性能较低.
redis
优点 : set和del指令的性能较高.
缺点 :
1.实现复杂,需要考虑超时,原子性,误删等情形.
2.没有等待锁的队列,只能在客户端自旋等锁,效率低下.
两者都可以在客户端实现可重入逻辑.
在apache的开源框架apache curator中,包含了对zookeeper分布式锁的实现,源码 : https://github.com/apache/curator/