Zookeeper是大數據生態圈中的重要組件,如果你做過相關開發的話,應該經常能看到它的身影。其由雅虎開源並成為Apache的頂級項目。用一句話對其進行定義就是:它是一套高吞吐的分布式協調系統。從中我們可以知道Zookeeper至少具有以下特點:
- 1.Zookeeper的主要作用是為分布式系統提供協調服務,包括但不限於:分布式鎖,統一命名服務,配置管理,負載均衡,主控服務器選舉以及主從切換等。
- 2.Zookeeper自身通常也以分布式形式存在。一個Zookeeper服務通常由多台服務器節點構成,只要其中超過一半的節點存活,Zookeeper即可正常對外提供服務,所以Zookeeper也暗含高可用的特性。客戶端可以通過TCP協議連接至任意一個服務端節點請求Zookeeper集群提供服務,而集群內部如何通信以及如何保持分布式數據一致性等細節對客戶端透明。如下圖所示

- 3.Zookeeper是以高吞吐量為目標進行設計的,故而在讀多寫少的場合有非常好的性能表現。如下圖所示

縱軸為每秒響應的客戶端請求數,橫軸為讀請求所占百分比。從圖中可以清晰的看到,隨着讀請求所占百分比的提高,Zookeeper的QPS也不斷提高。
Zookeeper具有高吞吐特性的主要原因有以下幾點:
1.Zookeeper集群的任意一個服務端節點都可以直接響應客戶端的讀請求(寫請求會不一樣些,下面會詳談),並且可以通過增加節點進行橫向擴展。這是其吞吐量高的主要原因
2.Zookeeper將全量數據存儲於內存中,從內存中讀取數據不需要進行磁盤IO,速度要快得多。
3.Zookeeper放松了對分布式數據的強一致性要求,即不保證數據實時一致,允許分布式數據經過一個時間窗口達到最終一致,這也在一定程度上提高了其吞吐量。
而寫請求,或者說事務請求,因為要進行不同服務器結點間狀態的同步,一定程度上會影響其吞吐量。故而簡單的增加Zookeeper的服務器節點數量,對其吞吐量的提升並不一定能起到正面效果。服務器節點增加,有利於提升讀請求的吞吐量,但會延長服務器節點數據的同步時間,必須視具體情況在這兩者之間取得一個平衡。
2. Zookeeper集群角色
在Zookeeper集群中,分別有Leader,Follower和Observer三種類型的服務器角色。
-
Leader
Leader服務器在整個正常運行期間有且僅有一台,集群會通過選舉的方式選舉出Leader服務器,由它同統一處理集群的事務性請求以及集群內各服務器的調度。 -
Follower
Follower的主要職責有以下幾點:- 1.參與Leader選舉投票
- 2.參與事務請求Proposal的投票
- 3.處理客戶端非事務請求(讀),並轉發事務請求(寫)給Leader服務器。
-
Observer
Observer是弱化版的Follower。其像Follower一樣能夠處理非事務也就是讀請求,並轉發事務請求給Leader服務器,但是其不參與任何形式的投票,不管是Leader選舉投票還是事務請求Proposal的投票。引入這個角色主要是為了在不影響集群事務處理能力的前提下提升集群的非事務處理的吞吐量。
3. Zookeeper的數據模型
Zookeeper將數據存儲於內存中,具體而言,Znode是存儲數據的最小單元。而Znode被以層次化的結構進行組織,形容一棵樹。其對外提供的視圖類似於Unix文件系統。樹的根Znode節點相當於Unix文件系統的根路徑。正如Unix中目錄下可以有子目錄一樣,Znode結點下也可以掛載子結點,最終形成如下所示結構。

以文件系統進行類比的話,Znode天然具有目錄和文件兩重屬性:即Znode既可以當做文件往里面寫東西,又可以當做目錄在下面掛載其他Znode。當然,由於Znode具有不同的類型,后半部分並不完全准確。
3.1 Znode的類型
Znode按其生命周期的長短可以分為持久結點(PERSISTENT)和臨時結點(EPHEMERAL);在創建時還可選擇是否由Zookeeper服務端在其路徑后添加一串序號用來區分同一個父結點下多個結點創建的先后順序。
經過組合就有以下4種Znode結點類型
-
1.持久結點(PERSISTENT)
最常見的Znode類型,一旦創建將在一直存在於服務端,除非客戶端通過刪除操作進行刪除。持久結點下可以創建子結點。 -
2.持久順序結點(PERSISTENT_SEQUENTIAL)
在具有持久結點基本特性的基礎上,會通過在結點路徑后綴一串序號來區分多個子結點創建的先后順序。這工作由Zookeeper服務端自動給我們做,只要在創建Znode時指定結點類型為該類型。

-
3.臨時結點(EPHEMERAL)
臨時結點的生命周期和客戶端會話保持一致。客戶端段會話存在的話臨時結點也存在,客戶端會話斷開則臨時結點會自動被服務端刪除。臨時結點下不能創建子結點。 -
4.臨時順序結點(EPHEMERAL_SEQUENTIAL)
具有臨時結點的基本特性,又有順序性。
3.2 Znode的結構
Znode結構主要由存儲於其中的數據信息和狀態信息兩部分構成,通過get 命令獲取一個Znode結點的信息如下

第一行存儲的是ZNode的數據信息,從cZxid開始就是Znode的狀態信息
Znode的狀態信息比較多,挑幾個比較重要的講
-
czxid:
即Created ZXID,表示創建該Znode結點的事務ID -
mzxid:
即Modified ZXID,表示最后一次更新該結點的事務ID -
version
該Znode結點的版本號。每個Znode結點被創建時版本號都為0,每更新一次都會導致版本號加1,即使更新前后Znode存儲的值沒有變化版本號也會加1。version值可以形象的理解為Znode結點被更新的次數。Znode狀態信息中的版本號信息,使得服務端可以對多個客戶端對同一個Znode的更新操作做並發控制。整個過程和java中的CAS有點像,是一種樂觀鎖的並發控制策略,而version值起到了沖突檢測的功能。客戶端拿到Znode的version信息,並在更新時附上這個version信息,服務端在更新Znode時必須必須比較客戶端的version和Znode的實際version,只有這兩個version一致時才會進行修改。
4. Zookeeper的事件監聽機制
Zookeeper中可以通過Watcher來實現事件監聽機制。客戶端可以向服務端注冊Watcher用以監聽某些事件,一旦該事件發生,服務端即會向客戶端發送一個通知。其主要工作流程如下圖所示

具體而言,Watcher是Zookeeper原生API中提供的事件監聽接口,用戶要實現事件監聽必須實現該接口並重寫process(WatchedEvent event)方法,該方法定義了客戶端在接收到服務端事件通知后的回調邏輯。究竟服務端的什么事件可以被監聽?按通知狀態划分有SyncConnected,Disconnected,Expired,AuthFailed等好多種,這里主要講下SyncConnected狀態下的幾種事件類型:
-
Node(-1)
客戶端與服務端成功建立會話 -
NodeCreated(1)
Watcher監聽的對應Znode被創建 -
NodeDeleted(2)
Watcher監聽的Znode被刪除 -
NodeDataChanged(3)
Watcher監聽的Znode的數據內容被改變,注意即使變更前后的數據內容完全一樣也會觸發該事件,或者理解成該事件的觸發條件是Znode的版本號變更也沒問題 -
NodeChildrenChanged(4)
Watcher監聽的對應Znode的子結點發生變更
Zookeeper的事件監聽機制有以下特性:
-
1.當監聽器監聽的事件被觸發,服務端會發送通知給客戶端,但通知信息中不包括事件的具體內容。以監聽ZNode結點數據變化為例,當Znode的數據被改變,客戶端會收到事件類型為NodeDataChanged的通知,但該Znode的數據改變成了什么客戶端無法從通知中獲取,需要客戶端在收到通知后手動去獲取。
-
2.Watcher是一次性的。一旦被觸發將會失效。如果需要反復進行監聽就需要反復進行注冊。這么設計是為了減輕服務端的壓力,但是對開發者而言卻是相當不友好,不過不用着急,可以通過一些Zookeeper的開源客戶端輕松實現對某一事件的永久監聽。
5. Zookeeper如何保證分布式數據一致性——ZAB協議
Zookeeper采用ZAB(Zookeeper Atomic Broadcast)協議來保證分布式數據一致性。ZAB並不是一種通用的分布式一致性算法,而是一種專為Zookeeper設計的崩潰可恢復的原子消息廣播算法。ZAB協議包括兩種基本模式:崩潰恢復模式和消息廣播模式。崩潰恢復模式主要用來在集群啟動過程,或者Leader服務器崩潰退出后進行新的Leader服務器的選舉以及數據同步;消息廣播模式主要用來進行事務請求的處理。下面就從這兩個方面來介紹
5.1 事務請求的處理流程
ZAB協議的核心是定義了對事務請求的處理方式,整個過程可以概括如下:
- 1.所有的事務請求都交由集群的Leader服務器來處理,Leader服務器會將一個事務請求轉換成一個Proposal(提議),並為其生成一個全局遞增的唯一ID,這個ID就是事務ID,即ZXID,Leader服務器對Proposal是按其ZXID的先后順序來進行排序和處理的。
- 2.之后Leader服務器會將Proposal放入每個Follower對應的隊列中(Leader會為每個Follower分配一個單獨的隊列),並以FIFO的方式發送給Follower服務器。
- 3.Follower服務器接收到事務Proposal后,首先以事務日志的方式寫入本地磁盤,並且在成功后返回Leader服務器一個ACK響應。
- 4.Leader服務器只要收到過半Follower的ACK響應,就會廣播一個Commit消息給Follower以通知其進行Proposal的提交,同時Leader自身也會完成Proposal的提交。
整個過程如下圖所示

5.2 Leader服務器的選舉流程
當集群中不存在Leader服務器時集群會進行Leader服務器的選舉,這通常存在於兩種情況:1.集群剛啟動時 2.集群運行時,但Leader服務器因故退出。集群中的服務器會向其他所有的Follower服務器發送消息,這個消息可以形象化的稱之為選票,選票主要由兩個信息組成,所推舉的Leader服務器的ID(即配置在myid文件中的數字),以及該服務器的事務ID,事務表示對服務器狀態變更的操作,一個服務器的事務ID越大,則其數據越新。整個過程如下所述:
- 1.Follower服務器投出選票(SID,ZXID),第一次每個Follower都會推選自己為Leader服務器,也就是說每個Follower第一次投出的選票是自己的服務器ID和事務ID。
- 2.每個Follower都會接收到來自於其他Follower的選票,它會基於如下規則重新生成一張選票:比較收到的選票和自己的ZXID的大小,選取其中最大的;若ZXID一樣則選取SID即服務器ID最大的。最終每個服務器都會重新生成一張選票,並將該選票投出去。
這樣經過多輪投票后,如果某一台服務器得到了超過半數的選票,則其將當前選為Leader。由以上分析可知,Zookeeper集群對Leader服務器的選擇具有偏向性,偏向於那些ZXID更大,即數據更新的機器。
整個過程如下圖所示

6. Zookeeper如何進行服務器故障的容錯
Zookeeper通過事務日志和數據快照來避免因為服務器故障導致的數據丟失。
- 事務日志是指服務器在更新內存數據前先將事務操作以日志的方式寫入磁盤,Leader和Follower服務器都會記錄事務日志。
- 數據快照是指周期性通過深度遍歷的方式將內存中的樹形結構數據轉入外存快照中。但要注意這種快照是"模糊"的,因為可能在做快照時內存數據發生了變化。但是因為Zookeeper本身對事務操作進行了冪等性保證,故在將快照加載進內存后會通過執行事務日志的方式來講數據恢復到最新狀態。
7. 參考資料
- 《從PAXOS到ZOOKEEPER分布式一致性原理與實踐》
- 《大數據日知錄》
