zookeeper 分布式锁原理
zookeeper 分布式鎖原理:
1?大家也許都很熟悉了多個線程或者多個進程間的共享鎖的實現(xiàn)方式了,但是在分布式場景中我們會面臨多個Server之間的鎖的問題,實現(xiàn)的復雜度比較高。利用基于google chubby原理開發(fā)的開源的zookeeper,可以使得這個問題變得簡單很多。下面介紹幾種可能的實現(xiàn)方式,并且對比每種實現(xiàn)方式的優(yōu)缺點。
1. 利用節(jié)點名稱的唯一性來實現(xiàn)共享鎖
ZooKeeper抽象出來的節(jié)點結(jié)構(gòu)是一個和unix文件系統(tǒng)類似的小型的樹狀的目錄結(jié)構(gòu)。ZooKeeper機制規(guī)定:同一個目錄下只能有一個唯一的文件名。例如:我們在Zookeeper目錄/test目錄下創(chuàng)建,兩個客戶端創(chuàng)建一個名為Lock節(jié)點,只有一個能夠成功。
算法思路: 利用名稱唯一性,加鎖操作時,只需要所有客戶端一起創(chuàng)建/test/Lock節(jié)點,只有一個創(chuàng)建成功,成功者獲得鎖。解鎖時,只需刪除/test/Lock節(jié)點,其余客戶端再次進入競爭創(chuàng)建節(jié)點,直到所有客戶端都獲得鎖。
基于以上機制,利用節(jié)點名稱唯一性機制的共享鎖算法流程如圖所示:
?
?
?
該共享鎖實現(xiàn)很符合我們通常多個線程去競爭鎖的概念,利用節(jié)點名稱唯一性的做法簡明、可靠。
由上述算法容易看出,由于客戶端會同時收到/test/Lock被刪除的通知,重新進入競爭創(chuàng)建節(jié)點,故存在"驚群現(xiàn)象"。
使用該方法進行測試鎖的性能列表如下:
?
?
?
?
總結(jié)?這種方案的正確性和可靠性是ZooKeeper機制保證的,實現(xiàn)簡單。缺點是會產(chǎn)生“驚群”效應(yīng),假如許多客戶端在等待一把鎖,當鎖釋放時候所有客戶端都被喚醒,僅僅有一個客戶端得到鎖。
2. 利用臨時順序節(jié)點實現(xiàn)共享鎖的一般做法
首先介紹一下,Zookeeper中有一種節(jié)點叫做順序節(jié)點,故名思議,假如我們在/lock/目錄下創(chuàng)建節(jié)3個點,ZooKeeper集群會按照提起創(chuàng)建的順序來創(chuàng)建節(jié)點,節(jié)點分別為/lock/0000000001、/lock/0000000002、/lock/0000000003。
ZooKeeper中還有一種名為臨時節(jié)點的節(jié)點,臨時節(jié)點由某個客戶端創(chuàng)建,當客戶端與ZooKeeper集群斷開連接,則開節(jié)點自動被刪除。
利用上面這兩個特性,我們來看下獲取實現(xiàn)分布式鎖的基本邏輯:
- 客戶端調(diào)用create()方法創(chuàng)建名為“l(fā)ocknode/guid-lock-”的節(jié)點,需要注意的是,這里節(jié)點的創(chuàng)建類型需要設(shè)置為EPHEMERAL_SEQUENTIAL。
- 客戶端調(diào)用getChildren(“l(fā)ocknode”)方法來獲取所有已經(jīng)創(chuàng)建的子節(jié)點,同時在這個節(jié)點上注冊上子節(jié)點變更通知的Watcher。
- 客戶端獲取到所有子節(jié)點path之后,如果發(fā)現(xiàn)自己在步驟1中創(chuàng)建的節(jié)點是所有節(jié)點中序號最小的,那么就認為這個客戶端獲得了鎖。
- 如果在步驟3中發(fā)現(xiàn)自己并非是所有子節(jié)點中最小的,說明自己還沒有獲取到鎖,就開始等待,直到下次子節(jié)點變更通知的時候,再進行子節(jié)點的獲取,判斷是否獲取鎖。
釋放鎖的過程相對比較簡單,就是刪除自己創(chuàng)建的那個子節(jié)點即可。
上面這個分布式鎖的實現(xiàn)中,大體能夠滿足了一般的分布式集群競爭鎖的需求。這里說的一般性場景是指集群規(guī)模不大,一般在10臺機器以內(nèi)。
不過,細想上面的實現(xiàn)邏輯,我們很容易會發(fā)現(xiàn)一個問題,步驟4,“即獲取所有的子點,判斷自己創(chuàng)建的節(jié)點是否已經(jīng)是序號最小的節(jié)點”,這個過程,在整個分布式鎖的競爭過程中,大量重復運行,并且絕大多數(shù)的運行結(jié)果都是判斷出自己并非是序號最小的節(jié)點,從而繼續(xù)等待下一次通知——這個顯然看起來不怎么科學。客戶端無端的接受到過多的和自己不相關(guān)的事件通知,這如果在集群規(guī)模大的時候,會對Server造成很大的性能影響,并且如果一旦同一時間有多個節(jié)點的客戶端斷開連接,這個時候,服務(wù)器就會像其余客戶端發(fā)送大量的事件通知——這就是所謂的驚群效應(yīng)。而這個問題的根源在于,沒有找準客戶端真正的關(guān)注點。
我們再來回顧一下上面的分布式鎖競爭過程,它的核心邏輯在于:判斷自己是否是所有節(jié)點中序號最小的。于是,很容易可以聯(lián)想的到的是,每個節(jié)點的創(chuàng)建者只需要關(guān)注比自己序號小的那個節(jié)點。
3. 利用臨時順序節(jié)點實現(xiàn)共享鎖的改進實現(xiàn)
下面是改進后的分布式鎖實現(xiàn),和之前的實現(xiàn)方式唯一不同之處在于,這里設(shè)計成每個鎖競爭者,只需要關(guān)注”locknode”節(jié)點下序號比自己小的那個節(jié)點是否存在即可。
算法思路:對于加鎖操作,可以讓所有客戶端都去/lock目錄下創(chuàng)建臨時順序節(jié)點,如果創(chuàng)建的客戶端發(fā)現(xiàn)自身創(chuàng)建節(jié)點序列號是/lock/目錄下最小的節(jié)點,則獲得鎖。否則,監(jiān)視比自己創(chuàng)建節(jié)點的序列號小的節(jié)點(比自己創(chuàng)建的節(jié)點小的最大節(jié)點),進入等待。
對于解鎖操作,只需要將自身創(chuàng)建的節(jié)點刪除即可。
具體算法流程如下圖所示:
?
?
使用上述算法進行測試的的結(jié)果如下表所示:
?
該算法只監(jiān)控比自身創(chuàng)建節(jié)點序列號小(比自己小的最大的節(jié)點)的節(jié)點,在當前獲得鎖的節(jié)點釋放鎖的時候沒有“驚群”。
總結(jié)?利用臨時順序節(jié)點來實現(xiàn)分布式鎖機制其實就是一種按照創(chuàng)建順序排隊的實現(xiàn)。這種方案效率高,避免了“驚群”效應(yīng),多個客戶端共同等待鎖,當鎖釋放時只有一個客戶端會被喚醒。
4. 使用menagerie
其實就是對方案3的一個封裝,不用自己寫代碼了。直接拿來用就可以了。
menagerie基于Zookeeper實現(xiàn)了java.util.concurrent包的一個分布式版本。這個封裝是更大粒度上對各種分布式一致性使用場景的抽象。其中最基礎(chǔ)和常用的是一個分布式鎖的實現(xiàn): org.menagerie.locks.ReentrantZkLock,通過ZooKeeper的全局有序的特性和EPHEMERAL_SEQUENTIAL類型znode的支持,實現(xiàn)了分布式鎖。具體做法是:不同的client上每個試圖獲得鎖的線程,都在相同的basepath下面創(chuàng)建一個EPHEMERAL_SEQUENTIAL的node。EPHEMERAL表示要創(chuàng)建的是臨時znode,創(chuàng)建連接斷開時會自動刪除; SEQUENTIAL表示要自動在傳入的path后面綴上一個自增的全局唯一后綴,作為最終的path。因此對不同的請求ZK會生成不同的后綴,并分別返回帶了各自后綴的path給各個請求。因為ZK全局有序的特性,不管client請求怎樣先后到達,在ZKServer端都會最終排好一個順序,因此自增后綴最小的那個子節(jié)點,就對應(yīng)第一個到達ZK的有效請求。然后client讀取basepath下的所有子節(jié)點和ZK返回給自己的path進行比較,當發(fā)現(xiàn)自己創(chuàng)建的sequential node的后綴序號排在第一個時,就認為自己獲得了鎖;否則的話,就認為自己沒有獲得鎖。這時肯定是有其他并發(fā)的并且是沒有斷開的client/線程先創(chuàng)建了node。
?
轉(zhuǎn)載于:https://www.cnblogs.com/myf008/p/8545587.html
總結(jié)
以上是生活随笔為你收集整理的zookeeper 分布式锁原理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: AngularJS——第3章 指令
- 下一篇: [HNOI2015]亚瑟王