圖解ZooKeeper的典型應用場景
zookeeper在很多框架中都有應用,例如:Dubbo,Hadoop,Kafka等,但典型的用法也就幾種,掌握了這幾種用法,再看zookeeper在相關框架中的應用就很輕鬆,下一篇文章將會詳細介紹zookeeper在dubbo中的使用,以便有一個更深刻的瞭解
本文參考了《從Paxos到ZooKeeper》,鑑於本文的定位是一篇科普性質的文章,因此對於一些諸如共享鎖和分散式佇列的具體實現沒有進行更詳細的描述,實際工作中需要實現時可以參考這本書
zookeeper的資料模型和檔案系統類似,每一個節點稱為znode,是zookeeper中的最小資料單元,每一個znode上可以報存資料和掛載子節點,從而構成一個層次化的屬性結構
可以建立如下四種節點(znode)
1.持久節點:節點建立後會一直存在zookeeper伺服器上,直到主動刪除
2.持久順序節點:每個節點都會為它的一級子節點維護一個順序
3.臨時節點:臨時節點的生命週期和客戶端的會話保持一致。當客戶端會話失效,該節點自動清理
4.臨時順序節點:在臨時節點上多了一個順序的特性
簡單演示一下常用的命令

-s : 建立順序節點
-e : 建立臨時節點
path : 路徑
data : 資料
acl : 許可權
create預設建立的是持久節點

執行完上述命令後,資料結構如下所示

這裡簡單說一下順序節點的特性。每次建立順序節點時,zk都會在路徑後面自動新增上10位的數字(計數器),例如 < path >0000000001,< path >0000000002,……這個計數器可以保證在同一個父節點下是唯一的。在zk內部使用了4個位元組的有符號整形來表示這個計數器,也就是說當計數器的大小超過2147483647時,將會發生溢位,每次在父節點下建立一個順序節點時,大小加1,如上圖的3到4
zookeeper提供了分散式資料釋出/訂閱,允許客戶端向服務端註冊一個watcher監聽,當服務端的一些指定事件觸發了這個watcher,那麼就會向指定客戶端傳送一個事件通知來實現分散式的通知功能。
簡單舉幾個watcher的事件型別

基礎知識講解完畢,下面正式分享zookeeper的作用
1.資料釋出/訂閱
資料釋出/訂閱(Publish/Subscribe)系統,即所謂的配置中心,顧明思義就是釋出者將資料釋出到zookeeper的一個或一系列的節點上,供訂閱者進行資料訂閱,進而達到動態獲取資料的目的,實現配置資訊的集中式管理和資料的動態更新。
zookeeper採用推拉結合的方式來實現釋出訂閱系統:客戶端向服務端註冊自己需要關注的節點,一旦該節點的資料發生變更,那麼服務端就會向相應的客戶端傳送Watcher事件通知,客戶端接收到這個訊息通知之後,需要主動到服務端獲取最新的資料。
程式總是需要配置的,如果程式分散部署在多臺機器上,要這個改變配置就變得困難。好吧,現在把這些配置全部放到zookeeper上去,儲存在zookeeper的某個目錄節點中,然後所有相關應用程式對這個目錄節點進行監控,一旦配置資訊發生變化,每個應用程式就會收到zookeeper的通知,然後從zookeeper中獲取新的配置資訊應用到系統中就好

2.負載均衡
每臺服務端在啟動時都會去zookeeper的servers節點下注冊臨時節點(註冊臨時節點是因為,當服務不可用時,這個臨時節點會消失,客戶端也就不會請求這個服務端),每臺客戶端在啟動時都會去servers節點下取得所有可用的工作伺服器列表,並通過一定的負載均衡演算法計算得出應該將請求發到哪個伺服器上

3. 生成分散式唯一ID
在過去的單庫單表型系統中,通常可以使用資料庫欄位自帶的auto_increment屬性來自動為每條記錄生成一個唯一的ID。但是分庫分表後,就無法在依靠資料庫的auto_increment屬性來唯一標識一條記錄了。此時我們就可以用zookeeper在分散式環境下生成全域性唯一ID。做法如下:每次要生成一個新Id時,建立一個持久順序節點,建立操作返回的節點序號,即為新Id,然後把比自己節點小的刪除即可。

4. Master選舉
Master選舉是一個在分散式系統中非常常見的應用場景。在分散式系統中,Master往往用來協調系統中的其他系統單元,具有對分散式系統狀態變更的決定權。例如,在一些讀寫分離的應用場景用,客戶端的寫請求往往是由Master來處理的,而在另一些場景中, Master則常常負負責處理一下複雜的邏輯,並將處理結果同步給叢集中其他系統單元。Master選舉可以說是zookeeper最典型的應用場景了
利用zookeeper的強一致性,能夠很好地保證在分散式高併發情況下節點的建立一定能保證全域性唯一性,即zookeeper將會保證客戶端無法重複建立一個已經存在的資料節點。也就是說,如果同時有多個客戶端請求建立同一個節點,那麼最終一定只有一個客戶端能夠建立成功。利用這個特性,就很容易在分散式環境中進行Master選舉

客戶端叢集往zookeeper上建立一個/master臨時節點。在這個過程中,只有一個客戶端能夠成功建立這個節點,那麼這個客戶端就成了master。同時其他沒有在zookeeper上成功建立節點的客戶端,都會在節點/master上註冊一個變更的watcher,用於監控當前的master機器是否存活,一旦發現當前的master掛了,那麼其餘的客戶端將會重新進行master選舉
5. 分散式鎖
在同一個JVM中,為了保證對一個資源的有序訪問,如往檔案中寫資料,可以用synchronized或者ReentrantLock來實現對資源的互斥訪問,如果2個程式在不同的JVM中,並且都要往同一個檔案中寫資料,如何保證互斥訪問呢?這時就需要用到分散式鎖了
目前分散式鎖的主流實現方式有兩種
1.利用redis setnex(key value) key不存在返回0,key存在返回1
2.zookeeper實現排他鎖,共享鎖(讀鎖)
這裡只簡單介紹一下排他鎖的實現方式
實現原理和master選舉類似,所有客戶端在/exclusive_lock節點下建立臨時子節點/exclusive_lock/lock,zookeeper會保證在所有的客戶端中,最終只有一個客戶端能夠建立成功,那麼就認為該客戶端獲取了鎖,其他沒有獲取到鎖的客戶端就需要到/exclusive_lock節點看上註冊一個子節點變更的watcher監聽,以便實時監聽到lock節點的變更情況
釋放鎖的情況有如下兩種
1.當前獲取鎖的客戶端發生宕機,那麼zookeeper上的這個臨時節點就會被刪除
2.正常執行完業務邏輯後,客戶端會主動將自己建立的臨時節點刪除
整個排他鎖的獲取和釋放流程可以用如下圖表示

6. 分散式佇列
如下圖,建立/queue作為一個佇列,然後每建立一個順序節點,視為一條訊息(節點儲存的資料即為訊息內容),生產者每次建立一個新節點,做為訊息傳送,消費者監聽queue的子節點變化(或定時輪詢),每次取最小節點當做消費訊息,處理完後,刪除該節點。相當於實現了一個FIFO(先進先出)的佇列。注:zookeeper強調的是CP(一致性),而非專為高併發、高效能場景設計的,如果在高併發,qps很高的情況下,分散式佇列需酌情考慮。
