一、四種節點
Zookeeper的數據存儲結構就像一棵樹,這棵樹由節點組成,這種節點叫做Znode。Znode分爲四種類型:
1.持久節點(PERSISTENT):默認的節點類型。創建節點的客戶端與zookeeper斷開連接後,該節點依舊存在。
2.持久節點順序節點(PERSISTENT_SEQUENTIAL):在創建節點時,Zookeeper根據創建的時間順序給該節點名稱進行編號。
3.臨時節點(EPHEMERAL):和持久節點相反,當創建節點的客戶端與zookeeper斷開連接後,臨時節點會被刪除。
4.臨時順序節點(EPHEMERAL_SEQUENTIAL):在創建節點時,Zookeeper根據創建的時間順序給該節點名稱進行編號;當創建節點的客戶端與zookeeper斷開連接後,臨時節點會被刪除。
Zookeeper的數據結構 |
順序節點 |
臨時節點1 |
臨時節點2 |
臨時節點3 |
二、Zookeeper分佈式鎖的原理
Zookeeper分佈式鎖應用了臨時順序節點。具體實現步驟如下:1.獲取鎖;2.釋放鎖
1、獲取鎖
(1)在Zookeeper當中創建一個持久節點ParentLock。當第一個客戶端想要獲得鎖時,需要在ParentLock這個節點下面創建一個臨時順序節點Lock1。
(2)Client1查找ParentLock下面所有的臨時順序節點並排序,判斷自己所創建的節點Lock1是不是順序最靠前的一個。如果是第一個節點,則成功獲得鎖。
(3)Client2查找ParentLock下面所有的臨時順序節點並排序,判斷自己所創建的節點Lock2是不是順序最靠前的一個,結果發現節點Lock2並不是最小的。於是,Client2向排序僅比它靠前的節點Lock1註冊Watcher,用於監聽Lock1節點是否存在。這意味着Client2搶鎖失敗,進入了等待狀態。
(4)這時候,如果又有一個客戶端Client3前來獲取鎖,則在ParentLock下載再創建一個臨時順序節點Lock3。Client3查找ParentLock下面所有的臨時順序節點並排序,判斷自己所創建的節點Lock3是不是順序最靠前的一個,結果同樣發現節點Lock3並不是最小的。於是,Client3向排序僅比它靠前的節點Lock2註冊Watcher,用於監聽Lock2節點是否存在。這意味着Client3同樣搶鎖失敗,進入了等待狀態。
這樣一來,Client1得到了鎖,Client2監聽了Lock1,Client3監聽了Lock2。這恰恰形成了一個等待隊列,很像是Java當中ReentrantLock所依賴的AQS(AbstractQueuedSynchronizer)。
2、釋放鎖
釋放鎖分爲兩種情況:1.任務完成,客戶端顯示釋放; 2.任務執行過程中,客戶端崩潰
(1)當任務完成時,Client1會顯示調用刪除節點Lock1的指令。
(2)獲得鎖的Client1在任務執行過程中,如果Duang的一聲崩潰,則會斷開與Zookeeper服務端的鏈接。根據臨時節點的特性,相關聯的節點Lock1會隨之自動刪除。
由於Client2一直監聽着Lock1的存在狀態,當Lock1節點被刪除,Client2會立刻收到通知。這時候Client2會再次查詢ParentLock下面的所有節點,確認自己創建的節點Lock2是不是目前最小的節點。如果是最小,則Client2順理成章獲得了鎖。同理,如果Client2也因爲任務完成或者節點崩潰而刪除了節點Lock2,那麼Client3就會接到通知。最終,Client3成功得到了鎖。
三、Zookeeper和Redis分佈式鎖的比較
從理解的難易程度角度(從低到高):數據庫 > 緩存(Redis) > Zookeeper
從實現的複雜性角度(從低到高):Zookeeper >= 緩存(Redis) > 數據庫
從性能角度(從高到低):緩存(Redis) > Zookeeper >= 數據庫
從可靠性角度(從高到低):Zookeeper > 緩存(Redis) > 數據庫
分佈式鎖 | 優點 | 缺點 |
Zookeeper |
1.有封裝好的框架,容易實現; 2.有等待所得隊列,大大提升搶鎖的效率 |
添加和刪除節點性能較低 |
Redis | set和del指令的性能較高 |
1.實現複雜,需要考慮超時,原子性,誤刪等情形; 2.沒有等待鎖的隊列,只能在客戶端自旋來等鎖,效率低下 |
有人說Zookeeper實現的分佈式鎖支持可重入,Redis實現的分佈式鎖不支持可重入,這是錯誤的觀點。兩者都可以在客戶端實現可重入邏輯。