利用zookeeper實(shí)現(xiàn)分布式鎖,支持讀寫(xiě)鎖嗜浮,公平與非公平鎖羡亩,可重入

一、前言

分布式鎖業(yè)界用的比較多的是redis和zookeeper作為鎖的介質(zhì)實(shí)現(xiàn)的危融。redis有比較成熟的開(kāi)箱即用的分布式鎖工具如Redisson畏铆,而zookeeper也有Curator實(shí)現(xiàn)了分布式鎖并可以開(kāi)箱即用,但是遺憾的是這兩者都沒(méi)實(shí)現(xiàn)讀寫(xiě)吉殃、公平鎖與非公平鎖辞居。根本原因可能是沒(méi)找到一套可實(shí)現(xiàn)讀寫(xiě)、公平與非公平的元語(yǔ)設(shè)計(jì)分布式鎖蛋勺,筆者在這里嘗試用zookeeper提供的基本操作和網(wǎng)上一些對(duì)于zookeeper實(shí)現(xiàn)鎖的思路推導(dǎo)下這個(gè)讀寫(xiě)瓦灶、公平與非公平鎖的元語(yǔ)。

二迫卢、分布式鎖元語(yǔ)設(shè)計(jì)

什么是分布式鎖元語(yǔ)倚搬?我們可以理解為我們?cè)O(shè)計(jì)一套分布式鎖方案其實(shí)是在做一道菜,做菜需要各種原始食材如雞肉乾蛤、蔥每界、姜捅僵、醬油等,在這些食材的基礎(chǔ)上我們可以做不同的菜眨层,而分布式鎖的加讀鎖庙楚、加寫(xiě)鎖、釋放鎖等操作就是我們要做的菜趴樱,分布式鎖就是加讀鎖馒闷、寫(xiě)鎖、釋放鎖等的集合的一桌菜叁征。

那zookeeper為我們提供了什么食材纳账?

1.zookeeper根據(jù)paxos算法為我們提供了一個(gè)強(qiáng)一致性的基礎(chǔ)環(huán)境。不了解的同學(xué)可以異步到一致性算法了解下強(qiáng)一致性的原理捺疼。

2.從用戶的角度看zookeeper疏虫,其實(shí)zookeeper是一個(gè)樹(shù)狀結(jié)構(gòu)的文件系統(tǒng),這個(gè)文件系統(tǒng)的粒度是節(jié)點(diǎn)啤呼,而節(jié)點(diǎn)的性質(zhì)有永久性的節(jié)點(diǎn)和臨時(shí)性的節(jié)點(diǎn)卧秘,如果某個(gè)客戶端對(duì)zookeeper創(chuàng)建了臨時(shí)節(jié)點(diǎn),則如果客戶端與zookeeper斷開(kāi)了連接zookeeper會(huì)自動(dòng)刪除剛剛客戶端創(chuàng)建的節(jié)點(diǎn)官扣。

3.zookeeper為我們提供了創(chuàng)建順序節(jié)點(diǎn)的操作翅敌。如果客戶端要?jiǎng)?chuàng)建的是順序節(jié)點(diǎn) 名稱是“node”,zookeeper會(huì)為這個(gè)名稱加上一個(gè)后綴代表順序惕蹄,如果“node00001”蚯涮。

4.zookeeper為我們提供了原子性的更新數(shù)據(jù)操作。zookeeper的每個(gè)節(jié)點(diǎn)是有一個(gè)版本的焊唬,如果客戶端想要更新某個(gè)節(jié)點(diǎn)的數(shù)據(jù)恋昼,客戶端需要先獲取這個(gè)節(jié)點(diǎn)原來(lái)的版本,然后客戶端更新的時(shí)候需要帶上這個(gè)版本赶促,zookeeper發(fā)現(xiàn)如果這個(gè)客戶端想要更新這個(gè)節(jié)點(diǎn)的數(shù)據(jù)液肌,會(huì)對(duì)比客戶端帶上來(lái)的這個(gè)版本跟目前在zookeeper上的版本是否一致,如果不一致代表已經(jīng)有其他的客戶端更改過(guò)這個(gè)節(jié)點(diǎn)鸥滨。這個(gè)跟JDK中的CAS是類似的嗦哆。

5.監(jiān)聽(tīng)機(jī)制⌒鲎遥客戶端可以監(jiān)聽(tīng)zookeeper的變化事件老速,如節(jié)點(diǎn)刪除事件,節(jié)點(diǎn)內(nèi)容變化事件凸主。

我們利用這些食材可以做什么橘券?

1.讀鎖監(jiān)聽(tīng)寫(xiě)鎖


圖1.1

如上圖1.1所示,lock_path是zookeeper的一個(gè)節(jié)點(diǎn)的名稱,這個(gè)節(jié)點(diǎn)名稱代表一個(gè)資源(鎖)的名稱旁舰,而下面四個(gè)節(jié)點(diǎn)代表四個(gè)客戶端都想要獲取鎖锋华。

看看客戶端節(jié)點(diǎn)的命名,如第一個(gè)節(jié)點(diǎn)A的命名是 w_identifyId_num,實(shí)際上這個(gè)命名代表的含義是 “讀寫(xiě)鎖類型_客戶端唯一標(biāo)識(shí)_代表客戶端的節(jié)點(diǎn)在lock_path下面的順序”箭窜,而上圖假設(shè)的是A,B,C,D是升序的毯焕,讀寫(xiě)鎖類型分別是w、r磺樱、r纳猫、r。

可以看出來(lái)因?yàn)锳節(jié)點(diǎn)是寫(xiě)鎖竹捉,并且處于lock_path下面的第一個(gè)子節(jié)點(diǎn)芜辕,因此此時(shí)A節(jié)點(diǎn)是占有鎖的,B活孩、C物遇、D是讀鎖,且因?yàn)閷?xiě)鎖A正在持有鎖憾儒,所以B、C乃沙、D不能占有鎖起趾,只能等待,等待的方式是向A節(jié)點(diǎn)注冊(cè)一個(gè)監(jiān)聽(tīng)A節(jié)點(diǎn)是否刪除的watch警儒,當(dāng)A節(jié)點(diǎn)刪除后训裆,B、C蜀铲、D會(huì)立馬收到通知边琉,而因?yàn)锽、C记劝、D都是讀鎖变姨,因此這三個(gè)節(jié)點(diǎn)同時(shí)獲得鎖。根據(jù)上面的邏輯厌丑,我們完成了讀鎖監(jiān)聽(tīng)寫(xiě)鎖的這道菜定欧。

2.寫(xiě)鎖監(jiān)聽(tīng)寫(xiě)鎖


圖1.2

如圖1.2,節(jié)點(diǎn)的含義跟上面讀鎖監(jiān)聽(tīng)寫(xiě)鎖的含義是一樣的怒竿,因?yàn)閷?xiě)鎖之間不能同時(shí)獲得鎖砍鸠,因此需要每個(gè)寫(xiě)鎖都監(jiān)聽(tīng)代表本客戶端的節(jié)點(diǎn)的前一個(gè)節(jié)點(diǎn)的刪除事件,當(dāng)前面的節(jié)點(diǎn)刪除后證明前一個(gè)寫(xiě)鎖釋放耕驰,本客戶端即可獲取鎖爷辱,并做一系列業(yè)務(wù)操作,做完后釋放鎖。根據(jù)這個(gè)邏輯即可實(shí)現(xiàn)寫(xiě)鎖監(jiān)聽(tīng)寫(xiě)鎖這道菜饭弓。

3.寫(xiě)鎖監(jiān)聽(tīng)讀鎖

有了上面兩個(gè)例子双饥,我猜讀者們覺(jué)得寫(xiě)鎖監(jiān)聽(tīng)讀鎖是一個(gè)很簡(jiǎn)單的操作。如果第一個(gè)節(jié)點(diǎn)是A讀鎖示启,此時(shí)A占有鎖兢哭,第二個(gè)節(jié)點(diǎn)B是寫(xiě)鎖,那是不是B節(jié)點(diǎn)注冊(cè)一個(gè)監(jiān)聽(tīng)A刪除的事件等待A鎖刪除就可以了夫嗓?我們來(lái)考慮下面這種情況

圖1.3

如上圖1.3迟螺,讀鎖A正在持有鎖,寫(xiě)鎖B在A后面監(jiān)聽(tīng)著A的刪除事件舍咖,此時(shí)有一個(gè)讀鎖C節(jié)點(diǎn)也要想獲取鎖矩父,按照我們對(duì)讀寫(xiě)鎖的理解,此時(shí)讀鎖正在占有鎖排霉,C也是讀鎖窍株,理應(yīng)C也可以獲取鎖,那現(xiàn)在我們假設(shè)C也獲取鎖并且C在自己的客戶端做自己的業(yè)務(wù)攻柠,此時(shí)A釋放了鎖也就是刪除了A節(jié)點(diǎn)球订,之后B收到了通知并獲取了鎖并在自己的客戶端做自己的業(yè)務(wù),此時(shí)會(huì)出現(xiàn)一個(gè)問(wèn)題瑰钮,就是B(寫(xiě)鎖)冒滩、C(讀鎖)同時(shí)在持有鎖。

有沒(méi)有解決的辦法呢浪谴?當(dāng)然有开睡,很簡(jiǎn)單的一個(gè)辦法就是C客戶端發(fā)現(xiàn)此時(shí)讀鎖A在持有鎖,C不會(huì)立刻占有鎖苟耻,而是繼續(xù)判斷本客戶端的節(jié)點(diǎn)與隊(duì)首之間是否有寫(xiě)鎖在等待篇恒,如果有則不獲取鎖,而是注冊(cè)監(jiān)聽(tīng)一個(gè)C節(jié)點(diǎn)前面最近的一個(gè)寫(xiě)鎖節(jié)點(diǎn)B的刪除事件凶杖。簡(jiǎn)單來(lái)說(shuō)就是讀鎖只會(huì)在隊(duì)首到本客戶端節(jié)點(diǎn)之間都是讀節(jié)點(diǎn)胁艰,才會(huì)第一時(shí)間獲取鎖,否則會(huì)監(jiān)聽(tīng)距離本客戶端前面距離最近的寫(xiě)鎖節(jié)點(diǎn)官卡。這個(gè)我稱它為公平鎖蝗茁。

但是這種方式有個(gè)缺點(diǎn)就是上面說(shuō)的,按照我們對(duì)讀寫(xiě)鎖的理解如果某個(gè)讀鎖C想獲得鎖寻咒,而當(dāng)時(shí)剛好又是讀鎖A占有鎖哮翘,則C應(yīng)該可以立馬占有鎖的,而上面這種方式確實(shí)不可以這樣毛秘,那有沒(méi)有方法實(shí)現(xiàn)這種邏輯呢饭寺?有的阻课,不過(guò)需要調(diào)整下上面說(shuō)的方案。

還是剛剛這種情況艰匙,不過(guò)我們變了下讀鎖獲取鎖的規(guī)則限煞,之前是讀鎖判斷自己符合規(guī)則(如判斷自己是否是隊(duì)頭或者判斷此刻是否有讀鎖持有鎖)后直接占有鎖,然后在客戶端做自己的業(yè)務(wù)邏輯员凝,其他節(jié)點(diǎn)是無(wú)法感知當(dāng)時(shí)有多少個(gè)讀鎖在持有鎖的∈鹱ぃ現(xiàn)在我們改的規(guī)則是創(chuàng)建lock_path的時(shí)候需要指定一個(gè)0作為內(nèi)容,我稱它為read_count健霹,代表某個(gè)時(shí)刻鎖的讀鎖持有個(gè)數(shù)旺上,讀鎖判斷自己符合獲取鎖的規(guī)則后,需要原子性的對(duì)read_count 加1糖埋,而寫(xiě)鎖獲取鎖的時(shí)候也需要增加一個(gè)條件宣吱,當(dāng)刪除事件觸發(fā)寫(xiě)鎖判斷自己是隊(duì)頭的時(shí)候,需要獲取read_count的值瞳别,并判斷read_count是否為0征候,如果大于0則說(shuō)明此時(shí)還有其他讀鎖在持有鎖,寫(xiě)鎖需要繼續(xù)監(jiān)聽(tīng)read_count的數(shù)據(jù)變化事件祟敛,每次read_count變化寫(xiě)鎖都需要判斷read_count是否等于0疤坝,如果等于0則寫(xiě)鎖占有鎖。

圖1.4

可能讀者看了上面這段話有點(diǎn)懵馆铁,沒(méi)關(guān)系卒煞,我們看看上圖1.4,用上面的圖來(lái)舉例子叼架。A是第一個(gè)要獲取鎖的客戶端,因此需要先創(chuàng)建一個(gè)lock_path代表鎖資源衣撬,賦值lock_path的值read_count為0乖订,然后客戶端A(讀鎖)在lock_path下面創(chuàng)建一個(gè)前綴為r_的臨時(shí)循序子節(jié)點(diǎn),然后客戶端A判斷自己是隊(duì)頭后具练,需要對(duì)read_count進(jìn)行原子性加1操作乍构,也就是read_count賦值為1,然后才可以占有鎖扛点。此時(shí)客戶端B(寫(xiě)鎖)在lock_path下也添加了個(gè)臨時(shí)順序子節(jié)點(diǎn)哥遮,然后B判斷自己不是隊(duì)頭后,監(jiān)聽(tīng)B前面的節(jié)點(diǎn)A的刪除事件陵究,此時(shí)客戶端C(讀鎖)也要獲取鎖眠饮,然后判斷自己是否是隊(duì)頭(如果是則獲取鎖),如果不是再判斷下read_count的值是否大于0铜邮,如果大于0則說(shuō)明此時(shí)是讀鎖持有鎖仪召,則C對(duì)read_count進(jìn)行原子性的加1寨蹋,成功后獲取鎖,此時(shí)客戶端A完成了自己的業(yè)務(wù)釋放了鎖刪除了對(duì)應(yīng)的節(jié)點(diǎn)扔茅,觸發(fā)了客戶端B已旧,B判斷了自己是隊(duì)頭后也并沒(méi)有立刻獲取鎖,而是先去lock_path下獲取read_count的值召娜,判斷是否等于0运褪,如果read_count大于0則說(shuō)明還有其他客戶端在持有鎖,需要繼續(xù)監(jiān)聽(tīng)read_count的變化事件玖瘸,直到read_count值等于0秸讹,B才可以占有鎖,這樣就解決了B(寫(xiě))店读、C(讀)同時(shí)占有鎖的問(wèn)題嗦枢。這個(gè)我成它為非公平鎖,因?yàn)楫?dāng)A獲占有鎖的時(shí)候屯断,如果后面持續(xù)的有讀鎖加入文虏,寫(xiě)鎖B是有可能一直得不到鎖的。

需要注意的是C對(duì)read_count加1是有可能失敗的殖演。zookeeper的node節(jié)點(diǎn)更新數(shù)據(jù)是需要帶上版本的氧秘,而zookeeper對(duì)某個(gè)節(jié)點(diǎn)每做一次數(shù)據(jù)更新,都會(huì)在節(jié)點(diǎn)的版本version加1的趴久。如C獲取了lock_path的版本是2丸相,并且判斷read_count是1,是大于0的彼棍,因此C想要對(duì)read_count進(jìn)行原子性加1灭忠,也就是在1的基礎(chǔ)上加1賦值成2,但是因?yàn)榭蛻舳薈要對(duì)zookeeper操作中間是有網(wǎng)絡(luò)的過(guò)程的座硕,是有延時(shí)的弛作,如果在這過(guò)程之間,A釋放了鎖對(duì)read_count減1賦值為0华匾,zookeeper對(duì)lock_path的version進(jìn)行了加一操作變成3映琳,然后觸發(fā)了客戶端B,B獲取read_count蜘拉,發(fā)現(xiàn)read_count等于0萨西,于是B占有了鎖,此時(shí)C的加1操作經(jīng)過(guò)一定的網(wǎng)絡(luò)延遲到達(dá)了zookeeper服務(wù)旭旭,要更新read_count的值為2谎脯,但是zookeeper發(fā)現(xiàn)C帶上來(lái)的version是2,而現(xiàn)在的lock_path節(jié)點(diǎn)的version是3您机,會(huì)向客戶端C返回一個(gè)異常穿肄,C獲取鎖失敗年局,因此這個(gè)時(shí)候C會(huì)監(jiān)聽(tīng)B的刪除事件。其中C用version去更新read_count就是上面說(shuō)的原子性加1咸产。

至此我們完成了“寫(xiě)鎖監(jiān)聽(tīng)讀鎖”這道菜的設(shè)計(jì)矢否,口味有公平鎖和非公平鎖,讀者可以看菜吃飯脑溢。

4.重入鎖

重入鎖的在分布式鎖的概念就是某個(gè)客戶端可以反復(fù)的對(duì)同一個(gè)資源進(jìn)行加鎖僵朗,操作很簡(jiǎn)單,就是客戶端在自己的客戶端里面做一個(gè)reentranLockCount標(biāo)識(shí)就可以了屑彻。

5.鎖釋放

鎖的釋放這里需要區(qū)分讀鎖和寫(xiě)鎖验庙,寫(xiě)鎖的釋放可以先刪掉本客戶端對(duì)應(yīng)的節(jié)點(diǎn),然后嘗試去刪lock_path,如果lock_path下面還有其他節(jié)點(diǎn)證明還有其他客戶端在等待所社牲,zookeeper是會(huì)返回一個(gè)刪除異常的粪薛。而讀鎖的釋放需要先刪掉自己客戶端對(duì)應(yīng)的節(jié)點(diǎn),然后對(duì)lock_path的read_count值原子性的減1操作搏恤,然后再嘗試去刪除lock_path违寿。

三、實(shí)現(xiàn)分布式鎖

上面針對(duì)分布式鎖的食材和制作的方法論都已經(jīng)準(zhǔn)備好了熟空,下面需要真正動(dòng)手了藤巢。在這里筆者已經(jīng)實(shí)現(xiàn)了一下,先放一個(gè)UML圖


圖2.1

如果看過(guò)JDK關(guān)于鎖的實(shí)現(xiàn)的同學(xué)來(lái)說(shuō)息罗,這個(gè)uml圖一定比較熟悉掂咒,這個(gè)是就是模仿里面的ReentrantReadWriteLock寫(xiě)的。核心的類是AbstractZkSynchronizer迈喉,里面的主要方法是封裝了一些zookeeper的操作绍刮,而AbstractSync類繼承AbstractZkSynchronizer,這個(gè)類主要的功能是將一些lock挨摸、tryLock等動(dòng)作轉(zhuǎn)化為對(duì)zookeeper的操作录淡,F(xiàn)airSync和NonFairSync都是繼承AbstractSync,分別是公平鎖與非公平鎖的實(shí)現(xiàn)油坝。而WriteLock和ReadLock都實(shí)現(xiàn)了ZkLock的接口,同樣是lock方法它們會(huì)根據(jù)自己是讀鎖還是寫(xiě)鎖去調(diào)AbstractSync的readLock(int) 和 acquire(int)方法刨裆。

筆者在這里會(huì)貼出主要的實(shí)現(xiàn)公平與非公平的寫(xiě)鎖和讀鎖的代碼和流程圖澈圈,感興趣的小伙伴可以到我的github看詳細(xì)的代碼,連接會(huì)在最后貼出來(lái)帆啃。

1.公平與非公平寫(xiě)鎖lock()實(shí)現(xiàn)

圖2.2

上圖2.2是寫(xiě)鎖的實(shí)現(xiàn)瞬女,公平讀鎖和非公平讀鎖都是這個(gè)流程,因?yàn)槲覀兌x的公平與非公平取決于讀鎖加鎖的流程努潘。下面再貼一下這個(gè)寫(xiě)鎖對(duì)應(yīng)的主要代碼

圖2.3

acquire(int)是寫(xiě)鎖lock()的委托實(shí)現(xiàn)诽偷±ぱВ可以看出來(lái)客戶端一進(jìn)來(lái)這個(gè)方法會(huì)首先根據(jù)isOwnerLock()判斷下本客戶端是否已經(jīng)持有過(guò)鎖,如果已經(jīng)持有鎖就會(huì)通過(guò)addReenTranLock(i)對(duì)鎖計(jì)數(shù)器進(jìn)行加1报慕。如果客戶端還沒(méi)持有鎖就會(huì)進(jìn)入下一個(gè)邏輯深浮,首先會(huì)用existsLockPath()判斷是否已經(jīng)存在lock_path,如果不存在就自己創(chuàng)建一個(gè)這樣的節(jié)點(diǎn)眠冈,然后通過(guò)addChildren(String)方法在lock_path下添加一個(gè)代表本客戶端的臨時(shí)順序子節(jié)點(diǎn)飞苇,并返回創(chuàng)建的節(jié)點(diǎn)的名稱,之后進(jìn)入一個(gè)while循環(huán)蜗顽,直到客戶端持有鎖后才退出布卡。而while循環(huán)的邏輯是先通過(guò)getChildrenList()獲取lock_path下所有的子節(jié)點(diǎn),然后判斷自己是否第一個(gè)節(jié)點(diǎn)雇盖,如果是需要再判斷read_count是否為0忿等,如果是就可以獲得鎖并跳出while循環(huán),如果read_count大于0則通過(guò)watchReadCount()監(jiān)聽(tīng)lock_path的read_count變化事件崔挖,有變化事件后就進(jìn)入下一次循環(huán)贸街,如果不是第一個(gè)節(jié)點(diǎn)就需要通過(guò)watchPreviousNode(String)監(jiān)聽(tīng)自己的客戶端對(duì)應(yīng)的節(jié)點(diǎn)的前一個(gè)節(jié)點(diǎn)的節(jié)點(diǎn)刪除事件,直到有刪除事件發(fā)生就進(jìn)入下一次循環(huán)判斷虚汛。

2.非公平讀鎖lock()實(shí)現(xiàn)

圖2.4

上圖2.4是實(shí)現(xiàn)非公平讀鎖的流程圖匾浪。筆者認(rèn)為這個(gè)流程圖的邏輯是比較清晰的,但是有個(gè)地方可能會(huì)有疑惑卷哩,就是監(jiān)聽(tīng)離本客戶端代表的節(jié)點(diǎn)的最近的一個(gè)寫(xiě)節(jié)點(diǎn)會(huì)有兩種結(jié)果蛋辈,一種是監(jiān)聽(tīng)成功,一種是監(jiān)聽(tīng)失敗将谊,但是無(wú)論失敗還是成功最終的結(jié)果都是會(huì)重新進(jìn)入“獲取鎖路徑下所有的子節(jié)點(diǎn)并由小到大排序”的這個(gè)步驟冷溶,其實(shí)很容易理解,如果監(jiān)聽(tīng)成功尊浓,并且成功等待到寫(xiě)節(jié)點(diǎn)的刪除事件逞频,證明本客戶端是有機(jī)會(huì)獲取鎖的所以會(huì)重復(fù)進(jìn)入剛說(shuō)的步驟,而監(jiān)聽(tīng)不成功是有可能你監(jiān)聽(tīng)前這個(gè)寫(xiě)節(jié)點(diǎn)在監(jiān)聽(tīng)之前就已經(jīng)被刪除了栋齿,所以還是會(huì)進(jìn)入剛說(shuō)的那個(gè)邏輯苗胀。

圖2.5

上圖2.5是非公讀鎖的主要代碼。代碼結(jié)構(gòu)跟上面寫(xiě)鎖的主要方法acquire(int)是類似的瓦堵,這里主要介紹attemptNonFairLock(String)

圖2.6

attemptNonFairLock(String)是嘗試用非公平的方式獲取讀鎖的實(shí)現(xiàn)基协,其中依賴startupAddReadLockCount() 和 waitProcessAddReadLockCount()方法,startupAddReadLockCount() 是當(dāng)讀鎖判斷自己是隊(duì)頭的時(shí)候要先對(duì)read_count進(jìn)行原子性加1操作菇用,?waitProcessAddReadLockCount()是當(dāng)讀鎖節(jié)判斷目前read_count大于0的時(shí)候需要對(duì)read_count進(jìn)行原子性加1操作澜驮,當(dāng)在方法外頭判斷read_count大于0的時(shí)候進(jìn)去waitProcessAddReadLockCount()方法的時(shí)候read_count是有可能被其他客戶端操作過(guò)的,因此在waitProcessAddReadLockCount()需要在判斷一次read_count的值惋鸥,如果等于0杂穷,則返回加1操作失敗悍缠。

2.公平讀鎖lock()實(shí)現(xiàn)

圖2.7


圖2.8

圖2.7、圖2.8分別是公平讀鎖的流程圖和代碼耐量》沈荆可以看出公平與非公平的流程是非常類似的,不同點(diǎn)是公平讀鎖判斷到如果read_count大于0的時(shí)候不會(huì)去立馬嘗試對(duì)read_count進(jìn)行原子性加1拴鸵,而是會(huì)再判斷本客戶端節(jié)點(diǎn)與首節(jié)點(diǎn)之間是否存在寫(xiě)鎖玷坠,如果存在寫(xiě)鎖就算read_count大于0也不獲取鎖,而是選擇監(jiān)聽(tīng)距離本客戶端前面最近的寫(xiě)節(jié)點(diǎn)進(jìn)行監(jiān)聽(tīng)劲藐。

3.非公平讀鎖tryLock(long,TimeUnit)

圖2.9
圖2.10

圖2.9與圖2.10分別是非公平讀鎖的tryLock(long, TimeUnit)的流程圖和代碼實(shí)現(xiàn)八堡,其中tryReadLock(int, long,TimeUnit)是tryLock(long,?TimeUnit)的委托實(shí)現(xiàn)。超時(shí)的機(jī)制主要依賴在while循環(huán)的條件里判斷是否超時(shí)聘芜,我們?cè)倏纯磜atchPreviousNode(String, long, TimeUnit)方法

圖2.11

watchPreviousNode(String, long,?TimeUnit)的功能是監(jiān)控前面寫(xiě)節(jié)點(diǎn)的刪除事件兄渺,在阻塞式獲取鎖的情況下,是會(huì)一直阻塞在監(jiān)聽(tīng)刪除事件這件事上的汰现,但是這里我們通過(guò)CountDownLatch的await(long, TimeUnit)有限時(shí)間等待這個(gè)節(jié)點(diǎn)刪除時(shí)間挂谍,如果超時(shí)了就會(huì)繼續(xù)往下執(zhí)行,然后在外層的while循環(huán)判斷已經(jīng)超時(shí)就會(huì)獲取鎖失敗瞎饲。

公平鎖和寫(xiě)鎖的tryLock(long,TimeUnit) 和tryLock()跟上面的非公平讀鎖tryLock(long,TimeUnit)的思路是類似的口叙,感興趣的小伙伴可以看下我在的github項(xiàng)目上的實(shí)現(xiàn),這里就不貼流程圖和代碼了嗅战。

四妄田、最后的話

以上是筆者對(duì)于zookeeper實(shí)現(xiàn)支持讀、寫(xiě)驮捍、公平疟呐、非公平的分布式鎖一些個(gè)人理解。如有邏輯錯(cuò)誤东且、筆誤启具、表述不清等或者讀者有不理解的地方請(qǐng)?jiān)谠u(píng)論區(qū)或者通過(guò)郵箱742953129@qq.com與我交流。

筆者用zookeeper實(shí)現(xiàn)的分布式鎖對(duì)應(yīng)的github項(xiàng)目地址:https://github.com/mirrormirrormirror/zkLock

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末珊泳,一起剝皮案震驚了整個(gè)濱河市鲁冯,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌色查,老刑警劉巖晓褪,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異综慎,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)勤庐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)示惊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)好港,“玉大人,你說(shuō)我怎么就攤上這事米罚【冢” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵录择,是天一觀的道長(zhǎng)拔莱。 經(jīng)常有香客問(wèn)我,道長(zhǎng)隘竭,這世上最難降的妖魔是什么塘秦? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮动看,結(jié)果婚禮上尊剔,老公的妹妹穿的比我還像新娘。我一直安慰自己菱皆,他們只是感情好须误,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著仇轻,像睡著了一般京痢。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上篷店,一...
    開(kāi)封第一講書(shū)人閱讀 51,146評(píng)論 1 297
  • 那天祭椰,我揣著相機(jī)與錄音,去河邊找鬼船庇。 笑死吭产,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的鸭轮。 我是一名探鬼主播臣淤,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼窃爷!你這毒婦竟也來(lái)了邑蒋?” 一聲冷哼從身側(cè)響起函匕,我...
    開(kāi)封第一講書(shū)人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤伐谈,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后贾陷,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體逮京,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡卿堂,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片草描。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡览绿,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出穗慕,到底是詐尸還是另有隱情饿敲,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布逛绵,位于F島的核電站怀各,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏术浪。R本人自食惡果不足惜瓢对,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望添吗。 院中可真熱鬧沥曹,春花似錦、人聲如沸碟联。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)鲤孵。三九已至壶栋,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間普监,已是汗流浹背贵试。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留凯正,地道東北人毙玻。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像廊散,于是被迫代替她去往敵國(guó)和親桑滩。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353