下面描述使用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(); }