天天看點

zookeeper分布式鎖

Zookeeper 架構

首先簡單介紹下 Zookeeper 叢集,一個 Zookeeper 叢集通常由一組機器組成,一般3~5台叢集就可以組成一個 Zookeeper 叢集。叢集拓撲圖基本如下:

zookeeper分布式鎖

Zookeeper 叢集中每一個節點都會在記憶體中維護目前的節點狀态,并且彼此之間保持着通信

Leader

Leader 節點整個 Zookeeper 叢集工作機制中的核心,主要工作是處理用戶端的讀寫請求,及叢集内部各服務的排程。注意隻有 leader 能夠處理寫請求。

Follower

處理用戶端的讀請求;如果有寫請求,則将寫請求轉發給 leader;參與 leader 選舉投票等。

Zookeeper 資料模型

Zookeeper 的資料模型是一棵類似 Unix 檔案系統的 ZNode Tree 即 ZNode 樹,術語叫做 ZNode。ZNode 是 Zookeeper 存儲資料的最小單元,每個 ZNode 可以儲存資料,也可以挂載子節點,其中根節點是 /。示意圖如下:

zookeeper分布式鎖

Zookeeper 主要提供了兩個核心功能:

  • 管理(存儲、讀取)用戶端送出的資料;
  • 為用戶端提供ZNode的監聽服務;

這裡就涉及到 Zookeeper 的兩個重要特性,就是它的 ZNode 模型與 Watcher 機制。

ZNode 模型

znode節點有4種類型:持久節點,臨時節點,持久有序節點,臨時有序節點

  • 持久節點(PERSISTENT):用戶端與 Zookeeper 斷開會話後,該節點依舊存在,直到執行删除操作才會删除節點。
  • 持久順序節點(PERSISTENT_SEQUENTIAL):另一種持久節點,不同的是zookeeper會給該節點名稱加上一個唯一單調遞增的整數,也就是持久有序節點
  • 臨時節點(EPHEMERAL):節點的生命周期和用戶端的會話綁定在一起,如果用戶端崩潰了或者關閉了與 ZooKeeper的連接配接,這個節點就會被自動删除
  • 臨時順序節點(EPHEMERAL_SEQUENTIAL):概念和上面類似,Zookeeper 也會給該節點進行順序編号。

ZNode 除了存儲使用者資料外,還有以下特點:

  • 包含 ZNode 修改/通路的時間、事務id(zxid),ACL 權限、版本等狀态資訊;
  • 所有的事務請求在 ZNode 端都是順序和原子性的;
  • 資料主要存儲在記憶體中,磁盤中儲存事務日志、快照資料等;

Watcher 機制

Watcher 機制也稱監聽機制,它是 Zookeeper 的關鍵特性,是通過 ZooKeeper 實作分布式釋出/訂閱、分布式鎖、叢集管理等功能的基礎。

ZooKeeper 用戶端獲得伺服器的資料或者變化,不是通過輪詢的模式,而是基于通知的機制,用戶端向 ZooKeeper 伺服器端注冊需要監聽的znode,如果被監聽的znode發生了改變(比如節點被删除,節點資料發變更),則會通知用戶端,需要強調的是這一個單次觸發的操作。

代碼示範 Zookeeper 監聽器

首先,目前有一個包含3個節點的 Zookeeper 叢集,我們根據 Zookeeper 版本引入了相應依賴,如下

zookeeper分布式鎖

示範代碼

  • 建立 ZNode
zookeeper分布式鎖

執行完這個單元測試後,我們通過指令行在服務端檢視一下該資料節點:

zookeeper分布式鎖
  • 删除 ZNode 節點,并監聽該節點的删除動作
zookeeper分布式鎖

代碼執行後,可以看到控制台列印出了znode被删除的日志:

zookeeper分布式鎖

再去服務端檢視該節點,可以看到已經不存在了:

zookeeper分布式鎖

Zookeeper分布式鎖的原理

Zookeeper分布式鎖恰恰應用了臨時順序節點。具體如何實作呢?讓我們來看一看詳細步驟:

擷取鎖

首先,在Zookeeper當中建立一個持久節點ParentLock。當第一個用戶端Client1想要獲得鎖時,需要在ParentLock這個節點下面建立一個臨時順序節點 Lock1。

之後,Client1查找ParentLock下面所有的臨時順序節點并排序,判斷自己所建立的節點Lock1是不是順序最靠前的一個。如果是第一個節點,則成功獲得鎖。

這時候,如果再有一個用戶端 Client2 前來擷取鎖,則在ParentLock下再建立一個臨時順序節點Lock2,Client2查找ParentLock下面所有的臨時順序節點并排序,判斷自己所建立的節點Lock2是不是順序最靠前的一個,結果發現節點Lock2并不是最小的。于是,Client2向排序僅比它靠前的節點Lock1注冊Watcher,用于監聽Lock1節點是否存在。這意味着Client2搶鎖失敗,進入了等待狀态。

這時候,如果又有一個用戶端Client3前來擷取鎖,則在ParentLock下載下傳再建立一個臨時順序節點Lock3。Client3查找ParentLock下面所有的臨時順序節點并排序,判斷自己所建立的節點Lock3是不是順序最靠前的一個,結果同樣發現節點Lock3并不是最小的。于是,Client3向排序僅比它靠前的節點Lock2注冊Watcher,用于監聽Lock2節點是否存在。這意味着Client3同樣搶鎖失敗,進入了等待狀态。

這樣一來,Client1得到了鎖,Client2監聽了Lock1,Client3監聽了Lock2

釋放鎖

釋放鎖分為兩種情況:

1.任務完成,用戶端顯示釋放

當任務完成時,Client1會顯示調用删除節點Lock1的指令。

2.任務執行過程中,用戶端崩潰

獲得鎖的Client1在任務執行過程中,如果發生了崩潰,則會斷開與Zookeeper服務端的連結。根據臨時節點的特性,與其相關聯的節點Lock1會随之自動删除。

由于Client2一直在監聽着Lock1節點,當Lock1節點被删除,Client2會立刻收到通知。這時候Client2會再次查詢ParentLock下面的所有節點,确認自己建立的節點Lock2是不是目前最小的節點。如果是最小,則Client2順理成章獲得了鎖。

同理,如果Client2也因為任務完成或者節點崩潰而删除了節點Lock2,那麼Client3就會接到通知。

參考:

ZooKeeper 源碼和實踐揭秘

如何用Zookeeper實作分布式鎖

一文了解 Zookeeper 基本原理與應用場景