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

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

三、代碼結構

-
org.apache.zookeeper.recipes.lock.ZNodeName:臨時節(jié)點名稱,能夠根據sequence進行排序。實現(xiàn)了Comparable接口。名稱中以'-'分割,并且在構造函數(shù)中提取sequence值。compareTo方法中,先根據sequence比較,如果相等,再根據前綴prefix比較:
04 compareTo.png 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,為實際的獲取鎖操作類。
org.apache.zookeeper.recipes.lock.LockListener:鎖監(jiān)控接口,定義了獲取鎖和釋放鎖的回調方法。
org.apache.zookeeper.recipes.lock.ProtocolSupport:主要提供retryOperation等同步操作。
-
org.apache.zookeeper.recipes.lock.WriteLock:互斥寫鎖的主要實現(xiàn),主要是選舉一個leader節(jié)點。通過調用lock()方法來嘗試獲取鎖??梢宰砸粋€監(jiān)聽器LockListener在獲取鎖或者是釋放鎖的時候調用。也可以通過調用 isOwner()來詢問是否擁有鎖:
05 lock.png
四、調試跟蹤
為了避免超時,更改org.apache.zookeeper.test.ClientBase類中的超時時間設置:

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

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

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


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

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


然后再創(chuàng)建一個客戶端,再次進行加鎖:



五、相關問題
- 對于分布式鎖的場景,如果創(chuàng)建的是臨時節(jié)點,當T1請求獲取鎖后,執(zhí)行相應業(yè)務邏輯,但是此時業(yè)務邏輯還沒有執(zhí)行完成,因網絡原因導致session過期,臨時節(jié)點就會被服務端刪除。這樣的話,其他節(jié)點也可以獲取鎖,分布式鎖就被破壞了。對于此種問題,可以通過創(chuàng)建持久性節(jié)點來解決。
- znode是否類似于Redis中的Key的概念?
可以把znode理解成Redis的一個Key,但是znode之間有層次關系。 - 在某些場景,經常用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了。 - 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包里面的鎖類默認都采用非公平模式。

