通過ZooKeeper的有序節點、節點路徑不回重複、還有節點刪除會觸發Wathcer事件的這些特性,我們可以實現分佈式鎖。
一、思路
- zookeeper中創建一個根節點Locks,用於後續各個客戶端的鎖操作。
- 當要獲取鎖的時候,在Locks節點下創建“Lock_序號”的零時有序節點(臨時節點爲了客戶端突發斷開連接,則此節點消失)。
- 如果沒有得到鎖,就監控排在自己前面的序號節點,等待它的釋放。
- 當前面的鎖被釋放後,觸發Process方法,然後繼續獲取當前子節點,判斷當前節點是不是第一個,是 返回鎖,否 獲取鎖失敗。
二、實現
在實現是要了解一個類 AutoResetEvent。AutoResetEvent 常常被用來在兩個線程之間進行信號發送。它有兩個重要的方法:
Set() :發送信號到等待線程以繼續其工作。
bool WaitOne():等待另一個線程發信號,只有收到信號,線程才繼續往下執行 ,會一直等待下去,返回值表示是否收到信號。
bool WaitOne(int millisecondsTimeout):等待指定時間,如果沒有收到信號繼續執行,返回值表示是否收到信號。
下面爲具體實現方法:
public class ZooKeeperLock { private MyWatcher myWatcher; private string lockNode; private org.apache.zookeeper.ZooKeeper zooKeeper; public ZooKeeperLock() { myWatcher = new MyWatcher(); } /// <summary> /// 獲取鎖 /// </summary> /// <param name="millisecondsTimeout">等待時間</param> /// <returns></returns> public async Task<bool> TryLock(int millisecondsTimeout = 0) { try { zooKeeper = new org.apache.zookeeper.ZooKeeper("127.0.0.1", 50000, new MyWatcher()); //創建鎖節點 if (await zooKeeper.existsAsync("/Locks") == null) await zooKeeper.createAsync("/Locks", null, Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); //新建一個臨時鎖節點 lockNode = await zooKeeper.createAsync("/Locks/Lock_", null, Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL); //獲取鎖下所有節點 var lockNodes = await zooKeeper.getChildrenAsync("/Locks"); lockNodes.Children.Sort(); //判斷如果創建的節點就是最小節點 返回鎖 if (lockNode.Split("/").Last() == lockNodes.Children[0]) return true; else { //當前節點的位置 var location = lockNodes.Children.FindIndex(n => n == lockNode.Split("/").Last()); //獲取當前節點 前面一個節點的路徑 var frontNodePath = lockNodes.Children[location - 1]; //在前面一個節點上加上Watcher ,當前面那個節點刪除時,會觸發Process方法 await zooKeeper.getDataAsync("/Locks/" + frontNodePath, myWatcher); //如果時間爲0 一直等待下去 if (millisecondsTimeout == 0) myWatcher.AutoResetEvent.WaitOne(); else //如果時間不爲0 等待指定時間後,返回結果 { var result = myWatcher.AutoResetEvent.WaitOne(millisecondsTimeout); if (result)//如果返回True,說明在指定時間內,前面的節點釋放了鎖(但是可能是中間節點主機宕機 導致,所以不一定觸發了Process方法就是得到了鎖。需要重新判斷是不是第一個節點) { //獲取鎖下所有節點 lockNodes = await zooKeeper.getChildrenAsync("/Locks"); //判斷如果創建的節點就是最小節點 返回鎖 if (lockNode.Split("/").Last() == lockNodes.Children[0]) return true; else return false; } else return false; } } } catch (KeeperException e) { await UnLock(); throw e; } return false; } /// <summary> /// 釋放鎖 /// </summary> /// <returns></returns> public async Task UnLock() { try { myWatcher.AutoResetEvent.Dispose();await zooKeeper.deleteAsync(lockNode); } catch (KeeperException e) { throw e; } } }
Process方法實現:
public class MyWatcher : Watcher { public AutoResetEvent AutoResetEvent; public MyWatcher() { this.AutoResetEvent = new AutoResetEvent(false); } public override Task process(WatchedEvent @event) { if (@event.get_Type() == EventType.NodeDeleted) { AutoResetEvent.Set(); } return null; } }