由於項目是要部署到多個節點上進行運行的,並且沒有使用主備模式,使用的是主主模式,所以當兩個節點上不同進程操作同一資源的時候,需要一個分佈式鎖對資源進行加鎖處理。
目前一般主流的方案都是使用redis來實現的,奈何當前項目處理的更多是離線數據而不是實時數據,基於業務考慮當前版本暫時沒有把redis引進來,所以只能基於ZK實現一個分佈式鎖(壓力不算太大,ZK還是抗的住的)。
本來是打算參考github上他人的實現自己寫一個的,發下Curator中就有現成的,那就先直接用現成的好了。
//todo 插入使用的demo
可以看出來,Curator已經封裝的特別好了…
InterProcessMutex設計思想:
InterProcessMutex是Curator實現的一種分佈式可重入排他鎖。該鎖實現的思想大致如下:
所有的線程都去同一個ZK路徑下創建臨時有序類型節點(EPHEMERAL_SEQUENTIAL,序號是ZK根據當前子節點數量自動添加整數序號)。
如果線程檢測到自身的節點序號最小,表明當前線程獲取到了鎖。其他節點會在序號比自己小的前一個節點上註冊一個Wacth,節點被刪除時能夠接收到通知,如果發現自己當前的序號是該路徑下最小的,表示自己獲取到的鎖,循環一直不停的執行該流程。
有兩種情況會釋放鎖,第一種是正常情況下事務處理結束釋放鎖,第二種是ZK檢測到客戶端連接超時了,主動將節點給刪除了。無論上述哪種情況,客戶端都不應當再持有鎖了。
(PS: 爲什麼不是往最小的節點註冊而是往比自己小的前一個節點上註冊,這樣做是爲了避免“羊羣效應”,如果有1000個線程都往最小的那個節點上註冊Watch,那麼鎖被釋放時,就會觸發往1000個線程發送通知,對ZK集羣會產生巨大的性能影響和網絡衝擊。由此也可以看出,這是一把公平鎖)
InterProcessMutex內部具體實現:
獲取鎖:
內部調用internalLock()方法獲取鎖,如果獲取鎖期間與ZK服務端之間的連接發生異常,會拋出異常:
-1和null表示獲取鎖期間會一直阻塞,當然也可以設置超時時間
看一下internalLock()方法:
內部有鎖重入邏輯
正確獲取鎖之後會在本地線程做一個記錄,然後返回true。真正加鎖的地方在attemptLock()方法內部:
點到attemptLock()方法內部看下:
createsTheLock()是在ZK路徑瞎創建臨時節點,注意此時並沒有獲取到鎖,核心其實就是一行代碼:
client.create().creatingParentsIfNeeded().withProtection().withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath(path, lockNodeBytes);
internalLockLoop()纔是加鎖,它判斷自己的節點序號是不是最小,是則返回true,表示獲取到鎖,否則阻塞等待
driver.getsTheLock用於判斷當前節點的序號是不是最小,返回的是前一個節點的序號(如果不是最小的話),還有是否當前節點是序號最小的標識符(true/false),如下所示:
如果當前節點序號是最小的,那麼predicateResults。getTheLock()得到的就是true,表明序號最小,可以持有鎖,否則回往欠一個節點上註冊WATCH,前一個節點鎖釋放了可以獲取鎖。
釋放鎖:
如果鎖不被當前線程持有,調用release()方法會拋出異常,否則將重入次數減一。如果發現減一之後可重入次數已經變成0了,那麼刪除ZK上的臨時節點(表示鎖完全被釋放,其他線程可以搶佔鎖了)。
guaranteed()方法可以保證客戶端失聯的情況下,ZK服務器也能刪除臨時節點:
幾種異常情況說明:
1.服務端手動把節點刪除了,客戶端還不知道,此時客戶端還可以重入麼???!!!這種算是異常情況,不是這把鎖要考慮的內容。
可以測試一下這種異常情況
這個是線程打印出來的日誌,三個連續的“get”是手動刪除的結果,說明節點被手動刪除這種異常情況不是鎖要考慮的
2.客戶端和服務端之間的網線被人拔了此時會怎麼處理???
心跳超過一定時間,臨時節點就會被刪除掉了,這樣可以保證ZK服務端沒有問題,那麼客戶端呢?!客戶端還是認爲自己持有鎖麼?
這個進程中的其他線程肯定不能持有鎖了,甚至這臺服務器上都不能持有這把鎖了。
------ 這種情況是不是隻有客戶端宕機了纔會發生,一般會不會考慮這種異常情況?!
還是說這個版本的代碼有缺陷...
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.7.1</version>
</dependency>
參考:
《從Paxos到Zookeeper 分佈式一致性原理與實踐》
https://www.jianshu.com/p/bc5e24e4f614(Curator客戶端創建分析)
https://www.jianshu.com/p/c2b4aa7a12f1(幾種分佈式鎖的實現方式)