2、
接下來咱們一塊兒來看看,多客戶端獲取及釋放zk分佈式鎖的整個流程及背後的原理。node
首先你們看看下面的圖,若是如今有兩個客戶端一塊兒要爭搶zk上的一把分佈式鎖,會是個什麼場景?redis
若是你們對zk還不太瞭解的話,建議先自行百度一下,簡單瞭解點基本概念,好比zk有哪些節點類型等等。框架
參見上圖。zk裏有一把鎖,這個鎖就是zk上的一個節點。而後呢,兩個客戶端都要來獲取這個鎖,具體是怎麼來獲取呢?分佈式
我們就假設客戶端A搶先一步,對zk發起了加分佈式鎖的請求,這個加鎖請求是用到了zk中的一個特殊的概念,叫作「臨時順序節點」。性能
簡單來講,就是直接在"my_lock"這個鎖節點下,建立一個順序節點,這個順序節點有zk內部自行維護的一個節點序號。spa
好比說,第一個客戶端來搞一個順序節點,zk內部會給起個名字叫作:xxx-000001。而後第二個客戶端來搞一個順序節點,zk可能會起個名字叫作:xxx-000002。你們注意一下,最後一個數字都是依次遞增的,從1開始逐次遞增。zk會維護這個順序。線程
因此這個時候,假如說客戶端A先發起請求,就會搞出來一個順序節點,你們看下面的圖,Curator框架大概會弄成以下的樣子:設計
你們看,客戶端A發起一個加鎖請求,先會在你要加鎖的node下搞一個臨時順序節點,這一大坨長長的名字都是Curator框架本身生成出來的。code
而後,那個最後一個數字是"1"。你們注意一下,由於客戶端A是第一個發起請求的,因此給他搞出來的順序節點的序號是"1"。排序
接着客戶端A建立完一個順序節點。還沒完,他會查一下"my_lock"這個鎖節點下的全部子節點,而且這些子節點是按照序號排序的,這個時候他大概會拿到這麼一個集合:
接着客戶端A會走一個關鍵性的判斷,就是說:唉!兄弟,這個集合裏,我建立的那個順序節點,是否是排在第一個啊?
若是是的話,那我就能夠加鎖了啊!由於明明我就是第一個來建立順序節點的人,因此我就是第一個嘗試加分佈式鎖的人啊!
bingo!加鎖成功!你們看下面的圖,再來直觀的感覺一下整個過程。
接着假如說,客戶端A都加完鎖了,客戶端B過來想要加鎖了,這個時候他會幹同樣的事兒:先是在"my_lock"這個鎖節點下建立一個臨時順序節點,此時名字會變成相似於:
你們看看下面的圖:
客戶端B由於是第二個來建立順序節點的,因此zk內部會維護序號爲"2"。
接着客戶端B會走加鎖判斷邏輯,查詢"my_lock"鎖節點下的全部子節點,按序號順序排列,此時他看到的相似於:
同時檢查本身建立的順序節點,是否是集合中的第一個?
明顯不是啊,此時第一個是客戶端A建立的那個順序節點,序號爲"01"的那個。因此加鎖失敗!
加鎖失敗了之後,客戶端B就會經過ZK的API對他的順序節點的上一個順序節點加一個監聽器。zk自然就能夠實現對某個節點的監聽。
若是你們還不知道zk的基本用法,能夠百度查閱,很是的簡單。客戶端B的順序節點是:
他的上一個順序節點,不就是下面這個嗎?
即客戶端A建立的那個順序節點!
因此,客戶端B會對:
這個節點加一個監聽器,監聽這個節點是否被刪除等變化!你們看下面的圖。
接着,客戶端A加鎖以後,可能處理了一些代碼邏輯,而後就會釋放鎖。那麼,釋放鎖是個什麼過程呢?
其實很簡單,就是把本身在zk裏建立的那個順序節點,也就是:
這個節點給刪除。
刪除了那個節點以後,zk會負責通知監聽這個節點的監聽器,也就是客戶端B以前加的那個監聽器,說:兄弟,你監聽的那個節點被刪除了,有人釋放了鎖。
此時客戶端B的監聽器感知到了上一個順序節點被刪除,也就是排在他以前的某個客戶端釋放了鎖。
此時,就會通知客戶端B從新嘗試去獲取鎖,也就是獲取"my_lock"節點下的子節點集合,此時爲:
集合裏此時只有客戶端B建立的惟一的一個順序節點了!
而後呢,客戶端B判斷本身竟然是集合中的第一個順序節點,bingo!能夠加鎖了!直接完成加鎖,運行後續的業務代碼便可,運行完了以後再次釋放鎖。
3、總結
其實若是有客戶端C、客戶端D等N個客戶端爭搶一個zk分佈式鎖,原理都是相似的。
- 你們都是上來直接建立一個鎖節點下的一個接一個的臨時順序節點
- 若是本身不是第一個節點,就對本身上一個節點加監聽器
- 只要上一個節點釋放鎖,本身就排到前面去了,至關因而一個排隊機制。
並且用臨時順序節點的另一個用意就是,若是某個客戶端建立臨時順序節點以後,不當心本身宕機了也不要緊,zk感知到那個客戶端宕機,會自動刪除對應的臨時順序節點,至關於自動釋放鎖,或者是自動取消本身的排隊。
最後,我們來看下用Curator框架進行加鎖和釋放鎖的一個過程:
其實用開源框架就是這點好,方便。這個Curator框架的zk分佈式鎖的加鎖和釋放鎖的實現原理,其實就是上面咱們說的那樣子。
可是若是你要手動實現一套那個代碼的話。仍是有點麻煩的,要考慮到各類細節,異常處理等等。因此你們若是考慮用zk分佈式鎖,能夠參考下本文的思路。
END
基於以上的一些zk的特性,咱們很容易得出使用zk實現分佈式鎖的落地方案:
-
使用zk的臨時節點和有序節點,每一個線程獲取鎖就是在zk建立一個臨時有序的節點,好比在/lock/目錄下。
-
建立節點成功後,獲取/lock目錄下的全部臨時節點,再判斷當前線程建立的節點是不是全部的節點的序號最小的節點
-
若是當前線程建立的節點是全部節點序號最小的節點,則認爲獲取鎖成功。
-
若是當前線程建立的節點不是全部節點序號最小的節點,則對節點序號的前一個節點添加一個事件監聽。
好比當前線程獲取到的節點序號爲
/lock/003
,而後全部的節點列表爲[/lock/001,/lock/002,/lock/003]
,則對/lock/002
這個節點添加一個事件監聽器。
若是鎖釋放了,會喚醒下一個序號的節點,而後從新執行第3步,判斷是否本身的節點序號是最小。
好比/lock/001
釋放了,/lock/002
監聽到時間,此時節點集合爲[/lock/002,/lock/003]
,則/lock/002
爲最小序號節點,獲取到鎖。
兩種方案的優缺點比較
學完了兩種分佈式鎖的實現方案以後,本節須要討論的是redis和zk的實現方案中各自的優缺點。
對於redis的分佈式鎖而言,它有如下缺點:
-
它獲取鎖的方式簡單粗暴,獲取不到鎖直接不斷嘗試獲取鎖,比較消耗性能。
-
redis分佈式鎖,其實須要本身不斷去嘗試獲取鎖,比較消耗性能。
對於zk分佈式鎖而言:
-
zookeeper天生設計定位就是分佈式協調,強一致性。鎖的模型健壯、簡單易用、適合作分佈式鎖。
-
若是獲取不到鎖,只須要添加一個監聽器就能夠了,不用一直輪詢,性能消耗較小。
可是zk也有其缺點:若是有較多的客戶端頻繁的申請加鎖、釋放鎖,對於zk集羣的壓力會比較大。