服務註冊中心之ZooKeeper系列(三) 實現分佈式鎖

  通過ZooKeeper的有序節點、節點路徑不回重複、還有節點刪除會觸發Wathcer事件的這些特性,我們可以實現分佈式鎖。

一、思路

  1. zookeeper中創建一個根節點Locks,用於後續各個客戶端的鎖操作。
  2. 當要獲取鎖的時候,在Locks節點下創建“Lock_序號”的零時有序節點(臨時節點爲了客戶端突發斷開連接,則此節點消失)。
  3. 如果沒有得到鎖,就監控排在自己前面的序號節點,等待它的釋放。
  4. 當前面的鎖被釋放後,觸發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;
        }
    }

本文源代碼在:分佈式實現代碼

如果你認爲文章寫的不錯,就點個【推薦】吧

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章