下面描述使用zookeeper实现分布式锁的算法流程,假设锁空间的根节点为/lock:html
步骤1中建立的临时节点可以保证在故障的状况下锁也能被释放,考虑这么个场景:假如客户端a当前建立的子节点为序号最小的节点,得到锁以后客户端所在机器宕机了,客户端没有主动删除子节点;若是建立的是永久的节点,那么这个锁永远不会释放,致使死锁;因为建立的是临时节点,客户端宕机后,过了必定时间zookeeper没有收到客户端的心跳包判断会话失效,将临时节点删除从而释放锁。java
另外细心的朋友可能会想到,在步骤2中获取子节点列表与设置监听这两步操做的原子性问题,考虑这么个场景:客户端a对应子节点为/lock/lock-0000000000,客户端b对应子节点为/lock/lock-0000000001,客户端b获取子节点列表时发现本身不是序号最小的,可是在设置监听器前客户端a完成业务流程删除了子节点/lock/lock-0000000000,客户端b设置的监听器岂不是丢失了这个事件从而致使永远等待了?这个问题不存在的。由于zookeeper提供的API中设置监听器的操做与读操做是原子执行的,也就是说在读子节点列表时同时设置监听器,保证不会丢失事件。node
最后,对于这个算法有个极大的优化点:假如当前有1000个节点在等待锁,若是得到锁的客户端释放锁时,这1000个客户端都会被唤醒,这种状况称为“羊群效应”;在这种羊群效应中,zookeeper须要通知1000个客户端,这会阻塞其余的操做,最好的状况应该只唤醒新的最小节点对应的客户端。应该怎么作呢?在设置事件监听时,每一个客户端应该对恰好在它以前的子节点设置事件监听,例如子节点列表为/lock/lock-0000000000、/lock/lock-000000000一、/lock/lock-0000000002,序号为1的客户端监听序号为0的子节点删除消息,序号为2的监听序号为1的子节点删除消息。算法
因此调整后的分布式锁算法流程以下:apache
<dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-recipes</artifactId> <version>4.0.0</version> </dependency>
public static void main(String[] args) throws Exception { //建立zookeeper的客户端 RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3); CuratorFramework client = CuratorFrameworkFactory.newClient("10.21.41.181:2181,10.21.42.47:2181,10.21.49.252:2181", retryPolicy); client.start(); //建立分布式锁, 锁空间的根节点路径为/curator/lock InterProcessMutex mutex = new InterProcessMutex(client, "/curator/lock"); mutex.acquire(); //得到了锁, 进行业务流程 System.out.println("Enter mutex"); //完成业务流程, 释放锁 mutex.release(); //关闭客户端 client.close(); }