zookeeper 分佈式鎖原理:java
1 你們也許都很熟悉了多個線程或者多個進程間的共享鎖的實現方式了,可是在分佈式場景中咱們會面臨多個Server之間的鎖的問題,實現的複雜度比較高。利用基於google chubby原理開發的開源的zookeeper,可使得這個問題變得簡單不少。下面介紹幾種可能的實現方式,而且對比每種實現方式的優缺點。node
1. 利用節點名稱的惟一性來實現共享鎖算法
ZooKeeper抽象出來的節點結構是一個和unix文件系統相似的小型的樹狀的目錄結構。ZooKeeper機制規定:同一個目錄下只能有一個惟一的文件名。例如:咱們在Zookeeper目錄/test目錄下建立,兩個客戶端建立一個名爲Lock節點,只有一個可以成功。服務器
算法思路: 利用名稱惟一性,加鎖操做時,只須要全部客戶端一塊兒建立/test/Lock節點,只有一個建立成功,成功者得到鎖。解鎖時,只需刪除/test/Lock節點,其他客戶端再次進入競爭建立節點,直到全部客戶端都得到鎖。併發
基於以上機制,利用節點名稱惟一性機制的共享鎖算法流程如圖所示:分佈式
該共享鎖實現很符合咱們一般多個線程去競爭鎖的概念,利用節點名稱惟一性的作法簡明、可靠。性能
由上述算法容易看出,因爲客戶端會同時收到/test/Lock被刪除的通知,從新進入競爭建立節點,故存在"驚羣現象"。測試
使用該方法進行測試鎖的性能列表以下:ui
總結 這種方案的正確性和可靠性是ZooKeeper機制保證的,實現簡單。缺點是會產生「驚羣」效應,假如許多客戶端在等待一把鎖,當鎖釋放時候全部客戶端都被喚醒,僅僅有一個客戶端獲得鎖。google
2. 利用臨時順序節點實現共享鎖的通常作法
首先介紹一下,Zookeeper中有一種節點叫作順序節點,故名思議,假如咱們在/lock/目錄下建立節3個點,ZooKeeper集羣會按照提起建立的順序來建立節點,節點分別爲/lock/000000000一、/lock/000000000二、/lock/0000000003。
ZooKeeper中還有一種名爲臨時節點的節點,臨時節點由某個客戶端建立,當客戶端與ZooKeeper集羣斷開鏈接,則開節點自動被刪除。
利用上面這兩個特性,咱們來看下獲取實現分佈式鎖的基本邏輯:
釋放鎖的過程相對比較簡單,就是刪除本身建立的那個子節點便可。
上面這個分佈式鎖的實現中,大致可以知足了通常的分佈式集羣競爭鎖的需求。這裏說的通常性場景是指集羣規模不大,通常在10臺機器之內。
不過,細想上面的實現邏輯,咱們很容易會發現一個問題,步驟4,「即獲取全部的子點,判斷本身建立的節點是否已是序號最小的節點」,這個過程,在整個分佈式鎖的競爭過程當中,大量重複運行,而且絕大多數的運行結果都是判斷出本身並不是是序號最小的節點,從而繼續等待下一次通知——這個顯然看起來不怎麼科學。客戶端無故的接受到過多的和本身不相關的事件通知,這若是在集羣規模大的時候,會對Server形成很大的性能影響,而且若是一旦同一時間有多個節點的客戶端斷開鏈接,這個時候,服務器就會像其他客戶端發送大量的事件通知——這就是所謂的驚羣效應。而這個問題的根源在於,沒有找準客戶端真正的關注點。
咱們再來回顧一下上面的分佈式鎖競爭過程,它的核心邏輯在於:判斷本身是不是全部節點中序號最小的。因而,很容易能夠聯想的到的是,每一個節點的建立者只須要關注比本身序號小的那個節點。
3. 利用臨時順序節點實現共享鎖的改進實現
下面是改進後的分佈式鎖實現,和以前的實現方式惟一不一樣之處在於,這裏設計成每一個鎖競爭者,只須要關注」locknode」節點下序號比本身小的那個節點是否存在便可。
算法思路:對於加鎖操做,可讓全部客戶端都去/lock目錄下建立臨時順序節點,若是建立的客戶端發現自身建立節點序列號是/lock/目錄下最小的節點,則得到鎖。不然,監視比本身建立節點的序列號小的節點(比本身建立的節點小的最大節點),進入等待。
對於解鎖操做,只須要將自身建立的節點刪除便可。
具體算法流程以下圖所示:
使用上述算法進行測試的的結果以下表所示:
該算法只監控比自身建立節點序列號小(比本身小的最大的節點)的節點,在當前得到鎖的節點釋放鎖的時候沒有「驚羣」。
總結 利用臨時順序節點來實現分佈式鎖機制其實就是一種按照建立順序排隊的實現。這種方案效率高,避免了「驚羣」效應,多個客戶端共同等待鎖,當鎖釋放時只有一個客戶端會被喚醒。
4. 使用menagerie
其實就是對方案3的一個封裝,不用本身寫代碼了。直接拿來用就能夠了。
menagerie基於Zookeeper實現了java.util.concurrent包的一個分佈式版本。這個封裝是更大粒度上對各類分佈式一致性使用場景的抽象。其中最基礎和經常使用的是一個分佈式鎖的實現: org.menagerie.locks.ReentrantZkLock,經過ZooKeeper的全局有序的特性和EPHEMERAL_SEQUENTIAL類型znode的支持,實現了分佈式鎖。具體作法是:不一樣的client上每一個試圖得到鎖的線程,都在相同的basepath下面建立一個EPHEMERAL_SEQUENTIAL的node。EPHEMERAL表示要建立的是臨時znode,建立鏈接斷開時會自動刪除; SEQUENTIAL表示要自動在傳入的path後面綴上一個自增的全局惟一後綴,做爲最終的path。所以對不一樣的請求ZK會生成不一樣的後綴,並分別返回帶了各自後綴的path給各個請求。由於ZK全局有序的特性,無論client請求怎樣前後到達,在ZKServer端都會最終排好一個順序,所以自增後綴最小的那個子節點,就對應第一個到達ZK的有效請求。而後client讀取basepath下的全部子節點和ZK返回給本身的path進行比較,當發現本身建立的sequential node的後綴序號排在第一個時,就認爲本身得到了鎖;不然的話,就認爲本身沒有得到鎖。這時確定是有其餘併發的而且是沒有斷開的client/線程先建立了node。