Zookeeper學習-08 Zookeeper 分布式鎖

一、設計

使用臨時順序znode來表示獲取鎖的請求,創(chuàng)建最小后綴數(shù)字znode的用戶成功拿到鎖。


01 設計.png

二、避免羊群效應(herd effect)

把鎖請求者按照后綴數(shù)字進行排隊,后綴數(shù)字小的鎖請求者先獲取鎖。如果所有的鎖請求者都watch鎖持有者,當代表鎖持有者的znode被刪除后,所有的鎖請求者都會都會通知到(驚著了),但是只有一個鎖請求者能拿到鎖。這就是羊群效應。


02鎖持有者.png

三、代碼結構

03代碼結構.png
  1. org.apache.zookeeper.recipes.lock.ZNodeName:臨時節(jié)點名稱,能夠根據sequence進行排序。實現(xiàn)了Comparable接口。名稱中以'-'分割,并且在構造函數(shù)中提取sequence值。compareTo方法中,先根據sequence比較,如果相等,再根據前綴prefix比較:


    04 compareTo.png
  2. org.apache.zookeeper.recipes.lock.ZooKeeperOperation:鎖實現(xiàn)接口。并且能夠實現(xiàn)重試操作。主要實現(xiàn)類為org.apache.zookeeper.recipes.lock.WriteLock中的私有內部類:org.apache.zookeeper.recipes.lock.WriteLock.LockZooKeeperOperation,為實際的獲取鎖操作類。

  3. org.apache.zookeeper.recipes.lock.LockListener:鎖監(jiān)控接口,定義了獲取鎖和釋放鎖的回調方法。

  4. org.apache.zookeeper.recipes.lock.ProtocolSupport:主要提供retryOperation等同步操作。

  5. org.apache.zookeeper.recipes.lock.WriteLock:互斥寫鎖的主要實現(xiàn),主要是選舉一個leader節(jié)點。通過調用lock()方法來嘗試獲取鎖??梢宰砸粋€監(jiān)聽器LockListener在獲取鎖或者是釋放鎖的時候調用。也可以通過調用 isOwner()來詢問是否擁有鎖:


    05 lock.png

四、調試跟蹤

為了避免超時,更改org.apache.zookeeper.test.ClientBase類中的超時時間設置:


06 clientBase.png

進入org.apache.zookeeper.recipes.lock.WriteLock中的lock()方法:


07 斷點lock方法.png

進入org.apache.zookeeper.recipes.lock.ProtocolSupport中的ensureExists方法,判斷目錄是否存在:


08 斷點ensureExists方法.png

如果不存在,則創(chuàng)建,且創(chuàng)建模式為PERSISTENT


09 PERSISTENT.png
10 斷點ensureExists方法.png

進入retryOperation方法,通過重試機制(默認重試次數(shù)retryCount為10次)執(zhí)行接口ZooKeeperOperation實現(xiàn)類的execute()方法,


11 斷點retryOperation方法.png

具體為執(zhí)行org.apache.zookeeper.recipes.lock.WriteLock.LockZooKeeperOperation類的execute()方法
努力嘗試查找最小后綴數(shù)字的znode節(jié)點成功拿到鎖。


12 execute方法.png

13 鎖判斷.png

然后再創(chuàng)建一個客戶端,再次進行加鎖:
14 執(zhí)行另一個鎖.png

15 另一個鎖目錄判斷.png

16案例執(zhí)行成功.png

五、相關問題

  1. 對于分布式鎖的場景,如果創(chuàng)建的是臨時節(jié)點,當T1請求獲取鎖后,執(zhí)行相應業(yè)務邏輯,但是此時業(yè)務邏輯還沒有執(zhí)行完成,因網絡原因導致session過期,臨時節(jié)點就會被服務端刪除。這樣的話,其他節(jié)點也可以獲取鎖,分布式鎖就被破壞了。對于此種問題,可以通過創(chuàng)建持久性節(jié)點來解決。
  2. znode是否類似于Redis中的Key的概念?
    可以把znode理解成Redis的一個Key,但是znode之間有層次關系。
  3. 在某些場景,經常用Redis做分布式鎖(setnx命令),只是redis沒有將請求者進行排隊, 與 zookeeper的分布式鎖有和區(qū)別?
    如果一個調用setnx的Redis客戶端crash,它設置的key還會存在,換言之鎖不會自動釋放。在ZooKeeper里面,我們用臨時節(jié)點表示鎖,如果ZooKeeper客戶端crash,它的鎖會自動釋放;ZooKeeper實現(xiàn)的鎖可以在鎖釋放時只通知一個鎖請求者,還保證鎖分配的FIFO。Zookeeper的鎖方案更加完備。
    另外Redis(https://redis.io/commands/setnx)本身也不推薦使用setnx了。
  4. zookeeper分布式鎖為了避免羊群效應,采用的是公平鎖。但是公平鎖有一個副作用:
    比如節(jié)點1獲得了鎖,節(jié)點2客戶端watch節(jié)點1,節(jié)點3客戶端watch節(jié)點2,如果此時節(jié)點2的客戶端心跳失敗,觸發(fā)watch機制,節(jié)點3的客戶端要更換watch節(jié)點,也就是會watch鎖持有者節(jié)點1,否則一旦節(jié)點1釋放鎖后,其他節(jié)點客戶端就會永遠感知不到。
    但是非公平鎖,不會存在這個問題。中間其它未持有鎖的client端的session失效,并不會對其他客戶端產生影響。在鎖競爭并不會特別激烈的場景下,非公平鎖的性能會更佳。正因為如此,jdk下JUC包里面的鎖類默認都采用非公平模式。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容