在《分佈式利器Zookeeper(一)》中對ZK進行了初步的介紹以及搭建ZK集羣環境,本篇博客將涉及的話題是:基於原生API方式操做ZK,Watch機制,分佈式鎖思路探討等。數據庫
什麼叫原生API操做ZK呢?實際上,利用zookeeper.jar這樣的就是基於原生的API方式操做ZK,由於這個原生API使用起來並非讓人很舒服,因而出現了zkclient這種方式,以致到後來基於Curator框架,讓人使用ZK更加方便。有一句話,Guava is to JAVA what Curator is to Zookeeper。
說明:緩存
在初始化Zookeeper時,有多種構造方法能夠選擇,有3個參數是必備的:connectionString(多個ZK SERVER之間以,分隔),sessionTimeout(就是zoo.cfg中的tickTime),Watcher(事件處理通知器)。服務器
須要注意的是ZK的鏈接是異步的,所以咱們須要CountDownLatch來幫助咱們確保ZK初始化完成。session
對於事件(WatchedEvent)而言,有狀態以及類型。數據結構
下面,咱們來看一看基於原生API方式的增刪改查:併發
注意,節點有2大類型,持久化節點、臨時節點。在此基礎上,又能夠分爲持久化順序節點(PERSISTENT_SEQUENTIAL)、臨時順序節點(EPHEMERAL_SEQUENTIAL)。app
節點類型只支持byte[],也就是說咱們是沒法直接給一個對象給ZK,讓ZK幫助咱們完成序列化操做的!框架
這裏須要注意的是,原生API對於ZK的操做實際上是分爲同步和異步2種方式的。異步
rc表示return code,就是返回碼,0即爲正常。分佈式
path是傳入API的參數,ctx也是傳入的參數。
注意在刪除過程當中,是須要版本檢查的,因此咱們通常提供-1跳過版本檢查機制。
ZK有watch事件,是一次性觸發的。當watch監控的數據發生變化,會通知設置了該監控的client,即watcher。Zookeeper的watch是有本身的一些特性的:
一次性:請牢記,just watch one time! 由於ZK的監控是一次性的,因此每次必須設置監控。
輕量:WatchedEvent是ZK進行watch通知的最小單元,整個數據結構包含:事件狀態、事件類型、節點路徑。注意ZK只是通知client節點的數據發生了變化,而不會直接提供具體的數據內容。
客戶端串行執行機制:注意客戶端watch回調的過程是一個串行同步的過程,這爲咱們保證了順序,咱們也應該意識到不能因一個watch的回調處理邏輯而影響了整個客戶端的watch回調。
下面咱們來直接看代碼:
必定得注意的是,監控該節點和監控該節點的子節點是2碼子事。
好比exists(path,true)監控的就是該path節點的create/delete/setData;getChildren(path,watcher)監控的就是該path節點下的子節點的變化(子節點的建立、修改、刪除都會監控到,並且事件類型都是同樣的,想想如何區分呢?給一個個人思路,就是咱們得先有該path下的子節點的列表,而後watch觸發後,咱們對比下該path下面的子節點SIZE大小及內容,就知道是增長的是哪一個子節點,刪除的是哪一個子節點了!)
getChildren(path,true)和getChildren(path,watcher)有什麼區別?前者是沿用上下文中的Watcher,然後者則是能夠設置一個新的Watcher的!(所以,要想作到一直監控,那麼就有2種方式,一個是注意每次設置成true,或者乾脆每次設置一個新的Watcher)
從上面的討論中,你大概能瞭解到原生的API其實功能上還不是很強大,有些還得咱們去操心,到後面爲你們介紹Curator框架,會有更好的方式進行處理。
首先,咱們不談Zookeeper是如何幫助咱們處理分佈式鎖的,而是先來想想,什麼是分佈式鎖?爲何須要分佈式鎖?有哪些場景呢?分佈式鎖的使用又有哪些注意的?分佈式鎖有什麼特性呢?
提及鎖,咱們天然想到Java爲咱們提供的synchronized/Lock,可是這顯然不夠,由於這隻能針對一個JVM中的多個線程對共享資源的操做。那麼對於多臺機器,多個進程對同一類資源進行操做的話,就是所謂分佈式場景下的鎖。
各個電商平臺常常搞的「秒殺」活動須要對商品的庫存進行保護、12306火車票也不能多賣,更不容許一張票被多我的買到、這樣的場景就須要分佈式鎖對共享資源進行保護!
既然,Java在分佈式場景下的鎖已經無能爲力,那麼咱們只能藉助其餘東西了!
對,沒錯,咱們可否藉助DB來實現呢?要知道DB是有一些特色供咱們利用的,好比DB自己就存在鎖機制(表鎖、行鎖),惟一約束等等。
假設,咱們的DB中有一張表T(id,methodname,ip,threadname,......),其中id爲主鍵,methodname爲惟一索引。
對於多臺機器,每臺機器上的多個線程而言,對一個方法method進行操做前,先select下T表中是否存在method這條記錄,若是沒有,就插入一條記錄到T中。固然可能併發select,可是因爲T表的惟一約束,使得只有一個請求能插入成功,即得到鎖。至於釋放鎖,就是方法執行完畢後delete這條記錄便可。
考慮一些問題:若是DB掛了,怎麼辦?若是因爲一些因素,致使delete沒有執行成功,那麼這條記錄會致使該方法不再能被訪問!爲何要先select,爲何不直接insert呢?性能如何呢?
爲了不單點,能夠主備之間實現切換;爲了不死鎖的產生,那麼咱們能夠有一個定時任務,按期清理T表中的記錄;先select後insert,實際上是爲了保證鎖的可重入性,也就是說,若是一臺IP上的某個線程獲取了鎖,那麼它能夠不用在釋放鎖的前提下,繼續得到鎖;性能上,若是大量的請求,將會對DB考驗,這將成爲瓶頸。
到這裏,還有一個明顯的問題,須要咱們考慮:上述的方案,雖然保證了只會有一個請求得到鎖,但其餘請求都獲取鎖失敗返回了,而沒有進行鎖等待!固然,咱們能夠經過重試機制,來實現阻塞鎖,不過數據庫自己的鎖機制能夠幫助咱們完成。別忘了select ... for update這種阻塞式的行鎖機制,commit進行鎖的釋放。並且對於for update這種獨佔鎖,若是長時間不提交釋放,會一直佔用DB鏈接,鏈接爆了,就跪了!
不說了,老朋友也只能幫咱們到這裏了!
既然說是緩存,相較DB,有更好的性能;既然說是分佈式,固然避免了單點問題;
好比,用Redis做爲分佈式鎖的setnx,這裏我就不細說了,總之分佈式緩存須要特別注意的是緩存的失效時間。(有效時間太短,搞很差業務尚未執行完畢,就釋放鎖了;有效時間過長,其餘線程白白等待,浪費了時間,拖慢了系統處理速度)
Zookeeper中臨時順序節點的特性:
第一,節點的生命週期和client回話綁定,即建立節點的客戶端回話一旦失效,那麼這個節點就會被刪除。(臨時性)
第二,每一個父節點都會維護子節點建立的前後順序,自動爲子節點分配一個×××數值,之後綴的形式自動追加到節點名稱中,做爲這個節點最終的節點名稱。(順序性)
那麼,基於臨時順序節點的特性,Zookeeper實現分佈式鎖的通常思路以下:
1.client調用create()方法建立「/root/lock_」節點,注意節點類型是EPHEMERAL_SEQUENTIAL
2.client調用getChildren("/root/lock_",watch)來獲取全部已經建立的子節點,並同時在這個節點上註冊子節點變動通知的Watcher
3.客戶端獲取到全部子節點Path後,若是發現本身在步驟1中建立的節點是全部節點中最小的,那麼就認爲這個客戶端得到了鎖
4.若是在步驟3中,發現不是最小的,那麼等待,直到下次子節點變動通知的時候,在進行子節點的獲取,判斷是否獲取到鎖
5.釋放鎖也比較容易,就是刪除本身建立的那個節點便可
上面的這種思路,在集羣規模很大的狀況下,會出現「羊羣效應」(Herd Effect):
在上面的分佈式鎖的競爭中,有一個細節,就是在getChildren上註冊了子節點變動通知Watcher,這有什麼問題麼?這其實會致使客戶端大量重複的運行,並且絕大多數的運行結果都是判斷本身並不是是序號最小的節點,從而繼續等待下一次通知,也就是不少客戶端作了不少無用功。更加要命的是,在集羣規模很大的狀況下,這顯然會對Server的性能形成影響,並且一旦同一個時間,多個客戶端斷開鏈接,服務器會向其他客戶端發送大量的事件通知,這就是所謂的羊羣效應!
出現這個問題的根源,其實在於,上述的思路並無找準客戶端的「痛點」:
客戶端的核心訴求在於判斷本身是不是最小的節點,因此說每一個節點的建立者其實不用關心全部的節點變動,它真正關心的應該是比本身序號小的那個節點是否存在!
1.client調用create()方法建立「/root/lock_」節點,注意節點類型是EPHEMERAL_SEQUENTIAL
2.client調用getChildren("/root/lock_",false)來獲取全部已經建立的子節點,這裏並不註冊任何Watcher
3.客戶端獲取到全部子節點Path後,若是發現本身在步驟1中建立的節點是全部節點中最小的,那麼就認爲這個客戶端得到了鎖
4.若是在步驟3中,發現不是最小的,那麼找到比本身小的那個節點,而後對其調用exist()方法註冊事件監聽
5.以後一旦這個被關注的節點移除,客戶端會收到相應的通知,這個時候客戶端須要再次調用getChildren("/root/lock_",false)來確保本身是最小的節點,而後進入步驟3
OK,talk is cheap show me the code,下一篇文章會爲你們帶來Zookeeper實現分佈式鎖的代碼。