Zookeeper分布式鎖安全嗎
Martin和Antirez爭(zhēng)論點(diǎn)
在之前的文章討論基于Redis的RedLock分布式鎖中,有提到劍橋分布式專家Martin指出,RedLock安全性并不高,并且其中有一個(gè)假設(shè)場(chǎng)景如下
假設(shè)存在多個(gè)實(shí)例A、B、C、D、E,同時(shí)存在客戶端1和2有如下場(chǎng)景
- 客戶端1請(qǐng)求實(shí)例A、B、C、D、E獲取鎖成功。
- 客戶端1開始操作共享資源,這時(shí)發(fā)生GC網(wǎng)絡(luò)暫停,stop-the-world。
- 在GC期間客戶端1持有的所有實(shí)例上的鎖過期。
- 客戶端2向?qū)嵗鼳、B、C、D、E請(qǐng)求獲取鎖,成功。
- 客戶端2操作共享資源,這時(shí)客戶端1從GC中恢復(fù),客戶端1無法感知鎖已經(jīng)過期,也操作共享資源導(dǎo)致沖突。
這個(gè)假設(shè)Redis之父Antirez指出在獲取鎖后發(fā)生的NPC(N:網(wǎng)絡(luò)延遲、P:進(jìn)程暫停、C:時(shí)鐘跳躍)問題RedLock無法處理,但這不僅僅是RedLock的問題,其余分布式鎖也有這個(gè)問題,如Zookeeper。
Zookeeper是否安全呢
上面的場(chǎng)景與我們理解的如果需要構(gòu)建更加安全的分布式鎖,首要參考的就是Zookeeper思想有沖突,那么我們應(yīng)該如何抉擇呢,下面參考Zookeeper的作者之一Flavio Junqueira寫的一篇博客Note on fencing and distributed locks
原文鏈接:https://fpj.systems/2016/02/10/note-on-fencing-and-distributed-locks/
Zookeeper構(gòu)建分布式鎖的步驟
Flavio Junqueira指出構(gòu)建的一種方式,步驟如下
- 客戶端嘗試創(chuàng)建一個(gè)znode節(jié)點(diǎn)如/lock,第一個(gè)客戶端創(chuàng)建成功后相當(dāng)于拿到了鎖,后續(xù)的客戶端再去創(chuàng)建/lock因?yàn)橐呀?jīng)存在那么創(chuàng)建會(huì)失敗。
- 持有鎖的客戶端在訪問共享資源后,將znode節(jié)點(diǎn)刪除,那么其它客戶端可以繼續(xù)創(chuàng)建znode節(jié)點(diǎn)。
- znode的節(jié)點(diǎn)應(yīng)該是臨時(shí)的,這樣才能保證如果客戶端突然崩潰,這個(gè)客戶端持有的znode節(jié)點(diǎn)才會(huì)被刪除,保證鎖的釋放。
這樣看起來非常完美,因?yàn)闆]有Redis那種設(shè)置自動(dòng)過期時(shí)間,可能因?yàn)闀r(shí)鐘跳躍導(dǎo)致鎖提前過期的情況,但真是這樣嗎,我們先來思考一個(gè)問題,客戶端突然崩潰zookeeper如何能快速感知到呢?
Zookeeper心跳檢測(cè)
每個(gè)客戶端都會(huì)和Zookeeper之間維護(hù)一個(gè)Session,這個(gè)Session依賴心跳維護(hù),如果Zookeeper在規(guī)定時(shí)間內(nèi)未收到客戶端心跳回復(fù),那么將認(rèn)為客戶端失去鏈接,這時(shí)就會(huì)將這個(gè)Session創(chuàng)建的所有臨時(shí)節(jié)點(diǎn)刪除。
Zookeeper的安全問題
正是因?yàn)閆ookeeper存在心跳檢測(cè)這個(gè)問題,那么可能出現(xiàn)以下場(chǎng)景。
- 客戶端1連接Zookeeper后創(chuàng)建一個(gè)znode臨時(shí)節(jié)點(diǎn)/lock成功。
- 客戶端1進(jìn)入長(zhǎng)時(shí)間的GC,進(jìn)程暫停。
- 客戶端1連接到Zookeeper的session長(zhǎng)時(shí)間未收到心跳回復(fù),并且超過過期時(shí)間,自動(dòng)刪除創(chuàng)建的臨時(shí)節(jié)點(diǎn)/lock。
- 客戶端2連接Zookeeper后同樣創(chuàng)建znode臨時(shí)節(jié)點(diǎn)/lock成功。
- 客戶端2開始操作共享資源,這時(shí)客戶端1恢復(fù)同樣操作共享資源,沖突產(chǎn)生。
這個(gè)場(chǎng)景就和上面分布式專家Martin提出的場(chǎng)景類似,在獲取鎖后發(fā)生NPC問題,這是單純依靠分布式鎖無法處理。
ZooKeeper的watch機(jī)制
ZooKeeper雖然單純依賴自己無法解決獲取鎖后的NPC安全性問題,但是其watch特性,將分布式鎖變地像一個(gè)單機(jī)鎖實(shí)現(xiàn)。
當(dāng)客戶端試圖創(chuàng)建一個(gè)臨時(shí)節(jié)點(diǎn)/lock時(shí),如果發(fā)現(xiàn)節(jié)點(diǎn)已經(jīng)創(chuàng)建,這時(shí)客戶端可以不立即失敗,客戶端可以進(jìn)入一個(gè)阻塞等待狀態(tài),等待當(dāng)/lock節(jié)點(diǎn)被刪除后,Zookeeper通過watch機(jī)制通知給客戶端,這樣的方式就好像JAVA中獲取單機(jī)鎖一樣方便。
Zookeeper和Redis對(duì)比
Zookeeper和Redis都能實(shí)現(xiàn)分布式鎖,優(yōu)勢(shì)如下
- 在客戶端和Zookeeper連接正常的情況下,客戶端可以持有鎖任意時(shí)長(zhǎng),這可以確保客戶端在持有鎖后操作共享資源并不會(huì)因?yàn)闃I(yè)務(wù)操作過長(zhǎng)而導(dǎo)致鎖過期,可以解決Redis過期時(shí)間到底設(shè)置多久的難題。
- Zookeeper支持的Watch機(jī)制,可以讓Zookeeper實(shí)現(xiàn)的分布式鎖,使用起來更加靈活,像使用本地鎖一樣。
劣勢(shì)
- Zookeeper與客戶端如果長(zhǎng)期沒有心跳那么鎖將自動(dòng)釋放。
- 性能不如Redis。
總結(jié)
分布式鎖可以由多種方式實(shí)現(xiàn),但是看完分布式專家Martin和Redis作者的討論后,在極端情況分布式鎖并不是完全安全的,Zookeeper也不能例外,所以這也就是分布式鎖都面臨著NPC三座大山的考驗(yàn),如果我們放在關(guān)鍵業(yè)務(wù)處理時(shí)并不能完全依靠分布式鎖,還需要有類似Martin提出的fecing token防護(hù)令牌兜底。