不懂 ZooKeeper?沒關係,這一篇給你講的明明白白
本來想系統回顧下 ZooKeeper的,可是網上沒找到一篇合自己胃口的文章,寫的差不多的,感覺大部分都是基於《從Paxos到ZooKeeper 分散式一致性原理與實踐》寫的,所以自己讀了一遍,加上項目中的使用,做個整理。加油,奧利給!
前言
面試常常被要求「熟悉分散式技術」,當年搞 「XXX管理系統」 的時候,我都不知道分散式系統是個啥。分散式系統是一個硬體或軟體組件分布在不同的網路電腦中上,彼此之間僅僅通過消息傳遞進行通訊和協調的系統。
電腦系統從集中式到分散式的變革伴隨著包括分散式網路、分散式事務、分散式數據一致性等在內的一系列問題和挑戰,同時也催生了一大批諸如ACID
、CAP
和 BASE
等經典理論的快速發展。
為了解決分散式一致性問題,湧現出了一大批經典的一致性協議和演算法,最為著名的就是二階段提交協議(2PC),三階段提交協議(3PC)和Paxos
演算法。Zookeeper
的一致性是通過基於 Paxos
演算法的 ZAB
協議完成的。一致性協議之前的文章也有介紹:「走進分散式一致性協議」從2PC、3PC、Paxos 到 ZAB,這裡就不再說了。
1. 概述
1.1 定義
ZooKeeper 官網是這麼介紹的:」Apache ZooKeeper 致力於開發和維護一個支援高度可靠的分散式協調的開源伺服器「
1.2 ZooKeeper是個啥
ZooKeeper 是 Apache 軟體基金會的一個軟體項目,它為大型「分散式計算」提供開源的分散式配置服務、同步服務和命名註冊。
Zookeeper 最早起源於雅虎研究院的一個研究小組。在當時,研究人員發現,在雅虎內部很多大型系統基本都需要依賴一個類似的系統來進行分散式協調,但是這些系統往往都存在分散式單點問題。所以,雅虎的開發人員就試圖開發一個通用的無單點問題的分散式協調框架,以便讓開發人員將精力集中在處理業務邏輯上,Zookeeper 就這樣誕生了。後來捐贈給了 Apache
,現已成為 Apache
頂級項目。
關於「ZooKeeper」這個項目的名字,其實也有一段趣聞。在立項初期,考慮到之前內部很多項目都是使用動物的名字來命名的(例如著名的Pig項目),雅虎的工程師希望給這個項目也取一個動物的名字。時任研究院的首席科學家 RaghuRamakrishnan 開玩笑地說:「再這樣下去,我們這兒就變成動物園了!」此話一出,大家紛紛表示就叫動物園管理員吧一一一因為各個以動物命名的分散式組件放在一起,雅虎的整個分散式系統看上去就像一個大型的動物園了,而 Zookeeper 正好要用來進行分散式環境的協調一一於是,Zookeeper 的名字也就由此誕生了。
ZooKeeper 是用於維護配置資訊,命名,提供分散式同步和提供組服務的集中式服務。所有這些類型的服務都以某種形式被分散式應用程式使用。每次實施它們時,都會進行很多工作來修復不可避免的 bug 和競爭條件。由於難以實現這類服務,因此應用程式最初通常會跳過它們,這會使它們在存在更改的情況下變得脆弱並且難以管理。即使部署正確,這些服務的不同實現也會導致管理複雜。
ZooKeeper 的目標是將這些不同服務的精華提煉為一個非常簡單的介面,用於集中協調服務。服務本身是分散式的,並且高度可靠。服務將實現共識,組管理和狀態協議,因此應用程式不需要自己實現它們。
1.3 ZooKeeper工作機制
ZooKeeper 從設計模式角度來理解:就是一個基於觀察者模式設計的分散式服務管理框架,它負責存儲和管理大家都關心的數據,然後接受觀察者的註冊,一旦這些數據的狀態發生變化,ZK 就將負責通知已經在 ZK 上註冊的那些觀察者做出相應的反應,從而實現集群中類似 Master/Slave 管理模式。
1.4 特性
-
ZooKeeper:一個領導者(leader),多個跟隨者(follower)組成的集群。
-
Leader 負責進行投票的發起和決議,更新系統狀態。
-
Follower 用於接收客戶請求並向客戶端返回結果,在選舉 Leader 過程中參與投票。
-
集群中只要有半數以上節點存活,Zookeeper 集群就能正常服務。
-
全局數據一致(單一視圖):每個 Server 保存一份相同的數據副本,Client 無論連接到哪個 Server,數據都是一致的。
-
順序一致性: 從同一客戶端發起的事務請求,最終將會嚴格地按照順序被應用到 ZooKeeper 中去。
-
原子性: 所有事務請求的處理結果在整個集群中所有機器上的應用情況是一致的,也就是說,要麼整個集群中所有的機器都成功應用了某一個事務,要麼都沒有應用。
-
實時性,在一定時間範圍內,client 能讀到最新數據。
-
可靠性: 一旦一次更改請求被應用,更改的結果就會被持久化,直到被下一次更改覆蓋。
1.5 設計目標
- 簡單的數據結構 :Zookeeper 使得分散式程式能夠通過一個共享的樹形結構的名字空間來進行相互協調,即Zookeeper 伺服器記憶體中的數據模型由一系列被稱為
ZNode
的數據節點組成,Zookeeper 將全量的數據存儲在記憶體中,以此來提高伺服器吞吐、減少延遲的目的。 - 可以構建集群 : Zookeeper 集群通常由一組機器構成,組成 Zookeeper 集群的每台機器都會在記憶體中維護當前伺服器狀態,並且每台機器之間都相互通訊。
- 順序訪問 : 對於來自客戶端的每個更新請求,Zookeeper 都會分配一個全局唯一的遞增編號,這個編號反映了所有事務操作的先後順序。
- 高性能 :Zookeeper 和 Redis 一樣全量數據存儲在記憶體中,100% 讀請求壓測 QPS 12-13W
1.6 數據結構
Zookeeper 數據模型的結構與 Unix 文件系統的結構相似,整體上可以看做是一棵樹,每個節點稱作一個 「ZNode」。每個 ZNode 默認能存儲 1MB 的數據,每個 ZNode 都可以通過其路徑唯一標識。
1.7 應用場景
ZooKeeper 是一個典型的分散式數據一致性解決方案,分散式應用程式可以基於 ZooKeeper 實現諸如數據發布/訂閱、負載均衡、命名服務、分散式協調/通知、集群管理、Master 選舉、分散式鎖和分散式隊列等功能
統一命名服務
在分散式系統中,通過使用命名服務,客戶端應用能夠根據指定名字來獲取資源或服務的地址,提供者等資訊。被命名的實體通常可以是集群中的機器,提供的服務地址,進程對象等等——這些我們都可以統稱他們為名字(Name)。其中較為常見的就是一些分散式服務框架(如RPC、RMI)中的服務地址列表。通過調用 Zookeeper 提供的創建節點的 API,能夠很容易創建一個全局唯一的 path,這個 path 就可以作為一個名稱。
阿里巴巴開源的分散式服務框架 Dubbo 就使用 ZooKeeper 來作為其命名服務,維護全局的服務地址列表。
數據發布與訂閱(配置中心)
發布與訂閱模型,即所謂的配置中心,顧名思義就是發布者將數據發布到 ZooKeeper 節點上,供訂閱者動態獲取數據,實現配置資訊的集中式管理和動態更新。例如全局的配置資訊,服務式服務框架的服務地址列表等就非常適合使用。
-
分散式環境下,配置文件管理和同步是一個常見問題
-
一個集群中,所有節點的配置資訊是一致的,比如 Hadoop 集群、集群中的資料庫配置資訊等全局配置
-
對配置文件修改後,希望能夠快速同步到各個節點上。
-
-
配置管理可交由 ZooKeeper 實現
- 可將配置資訊寫入 ZooKeeper 上的一個 Znode
- 各個節點監聽這個 Znode
- 一旦 Znode 中的數據被修改,ZooKeeper 將通知各個節點
統一集群管理
所謂集群管理無在乎兩點:是否有機器退出和加入、選舉 Master。
管理節點
-
分散式環境中,實時掌握每個節點的狀態是必要的,比如我們要知道集群中各機器狀態、收集各個機器的運行時狀態數據、伺服器動態上下線等。
-
交由 ZooKeeper 實現的方式
- 可將節點資訊寫入 ZooKeeper 上的一個 Znode
- 監聽這個 Znode 可獲取它的實時狀態變化
- 典型應用:HBase 中 Master 狀態監控和選舉。
Master選舉
在分散式環境中,相同的業務應用分布在不同的機器上,有些業務邏輯(例如一些耗時的計算,網路I/O處理),往往只需要讓整個集群中的某一台機器進行執行,其餘機器可以共享這個結果,這樣可以大大減少重複勞動,提高性能,於是這個master選舉便是這種場景下的碰到的主要問題。
利用 Zookeeper 的強一致性,能夠很好的保證在分散式高並發情況下節點的創建一定是全局唯一的,即:同時有多個客戶端請求創建 /currentMaster
節點,最終一定只有一個客戶端請求能夠創建成功。Zookeeper 通過這種節點唯一的特性,可以創建一個 Master 節點,其他客戶端 Watcher 監控當前 Master 是否存活,一旦 Master 掛了,其他機器再創建這樣的一個 Master 節點,用來重新選舉。
軟負載均衡
分散式系統中,負載均衡是一種很普遍的技術,為了保證高可用性,通常同一個應用或同一個服務的提供方都會部署多份,達到對等服務。可以是硬體的負載均衡,如 F5,也可以是軟體的負載,我們熟知的 Nginx,或者這裡介紹的 Zookeeper。
分散式協調/通知
Zookeeper 中特有的 「Watcher」 註冊與非同步通知機制,能夠很好的實現分散式環境下不同機器,甚至不同系統之間的協調和通知,從而實現對數據變更的實時處理。
使用方法通常是不同系統都對 ZK 上同一個 znode 進行註冊,監聽 znode 的變化(包括 znode 本身內容及子節點的),其中一個系統 update 了 znode,那麼另一個系統能夠收到通知,並作出相應處理。
- 心跳檢測中可以讓檢測系統和被檢測系統之間並不直接關聯起來,而是通過 ZK 上某個節點關聯,減少系統耦合;
- 系統調度模式中,假設某系統有控制台和推送系統兩部分組成,控制台的職責是控制推送系統進行相應的推送工作。管理人員在控制台作的一些操作,實際上是修改了 ZK 上某些節點的狀態,而 ZK 就把這些變化通知給他們註冊 Watcher 的客戶端,即推送系統,於是,作出相應的推送任務。
分散式鎖
分散式鎖,這個主要得益於 ZooKeeper 為我們保證了數據的強一致性。
鎖服務可以分為兩類,一個是保持獨佔,另一個是控制時序。
-
所謂保持獨佔,就是所有試圖來獲取這個鎖的客戶端,最終只有一個可以成功獲得這把鎖。通常的做法是把 zk 上的一個 znode 看作是一把鎖,通過
create znode
的方式來實現。所有客戶端都去創建/distribute_lock
節點,最終成功創建的那個客戶端也即擁有了這把鎖。 -
控制時序,就是所有試圖來獲取這個鎖的客戶端,最終都是會被安排執行,只是有個全局時序了。做法和上面基本類似,只是這裡
/distribute_lock
已預先存在,客戶端在它下面創建臨時有序節點(這個可以通過節點的屬性控制:CreateMode.EPHEMERAL_SEQUENTIAL
來指定)。ZK 的父節點(/distribute_lock
)維持一份 sequence,保證子節點創建的時序性,從而也形成了每個客戶端的全局時序。
個人感覺還是用 Redis 實現分散式鎖更加方便。
PS:阿里中間件團隊:「其實,ZK 並非天生就是為這些應用場景設計的,都是後來眾多開發者根據其框架的特性,利用其提供的一系列API介面(或者稱為原語集),摸索出來的典型使用方法。」
2. Hello ZooKeeper
ZooKeeper 的三種部署方式:
- 單機模式,即部署在單台機器上的一個 ZK 服務,適用於學習、了解 ZK 基礎功能
- 偽分布模式,即部署在一台機器上的多個(原則上大於3個)ZK 服務,偽集群,適用於學習、開發和測試
- 全分散式模式(複製模式),即在多台機器上部署服務,真正的集群模式,生產環境中使用
計劃寫三篇的,第二篇會實戰 coding,運用各種 API,到時候再裝集群,本節先來個單機玩~~
2.1 本地模式安裝部署
2.1.1 安裝前準備
-
安裝 Jdk
-
拷貝或下載 Zookeeper 安裝包到 Linux 系統下(這裡有個小問題,如果你下載 ZK 版本是3.5+ 的話,要下載 bin.tar.gz,愚笨的我最先沒看到官網說明,一頓操作各種報錯找不到 Main 方法)
-
解壓到指定目錄
tar -zxvf apache-zookeeper-3.5.7-bin.tar.gz
2.1.2 配置修改
-
將 zookeeper-3.5.7/conf 這個路徑下的
zoo_sample.cfg
修改為zoo.cfg
;mv zoo_sample.cfg zoo.cfg
-
打開 zoo.cfg 文件,修改 dataDir 路徑:
dataDir=XXX/zookeeper-3.5.7/zkData
2.1.3 操作 Zookeeper
- 啟動 Zookeeper:
bin/zkServer.sh start
/usr/local/bin/java
ZooKeeper JMX enabled by default
Using config: /home/sync360/test/apache-zookeeper-3.5.7-bin/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED
- 查看進程是否啟動:
jps
4020 Jps
4001 QuorumPeerMain
- 查看狀態:
bin/zkServer.sh status
/usr/local/bin/java
ZooKeeper JMX enabled by default
Using config: /home/apache-zookeeper-3.5.7-bin/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost.
Mode: standalone
- 啟動客戶端:
bin/zkCli.sh
Connecting to localhost:2181
2020-03-25 15:41:19,112 [myid:] - INFO [main:Environment@109] - Client environment:zookeeper.version=3.5.7-f0fdd52973d373ffd9c86b81d99842dc2c7f660e, built on 02/10/2020 11:30 GMT
...
2020-03-25 15:41:19,183 [myid:] - INFO [main:ClientCnxn@1653] - zookeeper.request.timeout value is 0. feature enabled=
Welcome to ZooKeeper!
...
WATCHER::
WatchedEvent state:SyncConnected type:None path:null
-
退出客戶端:
quit
-
停止 Zookeeper:
bin/zkServer.sh stop
2.2 常用命令
命令基本語法 | 功能描述 |
---|---|
help | 顯示所有操作命令 |
ls path [watch] | 使用 ls 命令來查看當前znode中所包含的內容 |
ls2 path [watch] | 查看當前節點數據並能看到更新次數等數據 |
create | 普通創建-s 含有序列-e 臨時(重啟或者超時消失) |
get path [watch] | 獲得節點的值 |
set | 設置節點的具體值 |
stat | 查看節點狀態 |
delete | 刪除節點 |
rmr | 遞歸刪除節點 |
ls 查看當前 zk 中所包含的內容
[zk: localhost:2181(CONNECTED) 1] ls /
[lazyegg, zookeeper]
create 創建一個新的 znode
[zk: localhost:2181(CONNECTED) 2] create /test
Created /test
get 查看新的 znode 的值
[zk: localhost:2181(CONNECTED) 4] get /test
null
可以看到值為 null,我們剛才設置了一個沒有值的節點,也可以通過 create /zoo dog
直接創建有內容的節點
set 對 zk 所關聯的字元串進行設置
set /test hello
delete 刪除節點
delete /test
2.3 配置參數解讀
在 Zookeeper 的設計中,如果是集群模式,那所有機器上的 zoo.cfg 文件內容應該都是一致的。
Zookeeper 中的配置文件 zoo.cfg
中參數含義解讀如下:
-
tickTime =2000:通訊心跳數
Zookeeper 使用的基本時間,伺服器之間或客戶端與伺服器之間維持心跳的時間間隔,也就是每個 tickTime時間就會發送一個心跳,時間單位為毫秒
它用於心跳機制,並且設置最小的 session 超時時間為兩倍心跳時間。(session的最小超時時間是2*tickTime);
-
initLimit =10:主從初始通訊時限,集群中的 Follower 跟隨者伺服器與 Leader 領導者伺服器之間初始連接時能容忍的最多心跳數(tickTime的數量),用它來限定集群中的 ZK 伺服器連接到 Leader 的時限;
-
syncLimit =5:主從同步通訊時限,集群中 Leader 與 Follower 之間的最大響應時間單位,假如響應超過
syncLimit * tickTime
,Leader 認為 Follwer 死掉,從伺服器列表中刪除 Follwer; -
dataDir:數據文件目錄+數據持久化路徑;
-
clientPort =2181:客戶端連接埠
3. 你要知道的概念
- ZooKeeper 本身就是一個分散式程式(只要半數以上節點存活,ZooKeeper 就能正常服務)。
- 為了保證高可用,最好是以集群形態來部署 ZooKeeper,這樣只要集群中大部分機器是可用的(能夠容忍一定的機器故障),那麼 ZooKeeper 本身仍然是可用的。
- ZooKeeper 將數據保存在記憶體中,這也就保證了高吞吐量和低延遲(但是記憶體限制了能夠存儲的容量不太大,此限制也是保持 znode 中存儲的數據量較小的進一步原因)。
- ZooKeeper 是高性能的。 在「讀」多於「寫」的應用程式中尤其的高性能,因為「寫」會導致所有的伺服器間同步狀態。(「讀」多於「寫」是協調服務的典型場景。)
- ZooKeeper 底層其實只提供了兩個功能:
- 管理(存儲、讀取)用戶程式提交的數據
- 為用戶程式提交數據節點監聽服務
這裡引入一個簡單的例子,逐個介紹一些 ZK 中的概念。
在分散式系統中經常會遇到這種情況,多個應用讀取同一個配置。例如:Client1,Client2 兩個應用都會讀取配置 B 中的內容,一旦 B 中的內容出現變化,就會通知 Client1 和 Client2。
一般的做法是在 Client1,Client2 中按照時鐘頻率詢問 B 的變化,或者使用觀察者模式來監聽 B 的變化,發現變化以後再更新兩個客戶端。那麼 ZooKeeper 如何協調這種場景?
這兩個客戶端連接到 ZooKeeper 的伺服器,並獲取其中存放的 B。保存 B 值的地方在 ZooKeeper 服務端中就稱為 ZNode。
3.1 數據節點(Znode)
在談到分散式的時候,我們通常說的「節點”是指組成集群的每一台機器。然而,在 Zookeeper 中,「節點”分為兩類,第一類同樣是指構成集群的機器,我們稱之為「機器節點」;第二類則是指數據模型中的數據單元,我們稱之為「數據節點」一一ZNode。上圖中的 A、B 就是一個數據結點。
Zookeeper 將所有數據存儲在記憶體中,數據模型是一棵樹(Znode Tree),由斜杠(/)進行分割的路徑,就是一個 Znode,例如 /Configuration/B
。每個 Znode 上都會保存自己的數據內容,同時還會保存一系列屬性資訊。
在 Zookeeper 中,Znode 可以分為持久節點和臨時節點兩類。
- 所謂持久節點是指一旦這個 ZNode 被創建了,除非主動進行 ZNode 的移除操作,否則這個 ZNode 將一直保存在 Zookeeper 上。
- 而臨時節點就不一樣了,它的生命周期和客戶端會話綁定,一旦客戶端會話失效,那麼這個客戶端創建的所有臨時節點都會被移除。
另外,ZooKeeper 還允許用戶為每個節點添加一個特殊的屬性:SEQUENTIAL。也被叫做 順序結點,一旦節點被標記上這個屬性,那麼在這個節點被創建的時候,Zookeeper 會自動在其節點名後面追加上一個整型數字,這個整型數字是一個由父節點維護的自增數字。
3.2 事件監聽器(Watcher)
上面說了 ZooKeeper 用來存放數據的 ZNode,並且把 B 的值存儲在裡面。如果 B 被更新了,兩個客戶端(Client1、Client2)如何獲得通知呢?
Zookeeper 允許用戶在指定節點上註冊一些 Watcher,當 Znode 發生變化時,將觸發並刪除一個 watch。當 watch 被觸發時客戶端會收到一個數據包,指示 znode 已經被修改。如果客戶端和 ZooKeeper 伺服器之間的連接中斷,客戶端將收到本地通知。該機制是 Zookeeper 實現分散式協調服務的重要特性。
3.6.0中的新增功能:客戶端還可以在 znode 上設置永久性的遞歸監視,這些監視在觸發時不會刪除,並且會以遞歸方式觸發已註冊 znode 以及所有子 znode 的更改。
ZooKeeper 客戶端(Client)會在指定的節點(/Configuration/B)上註冊一個 Watcher,ZNode 上的 B 被更新的時候,服務端就會通知 Client1 和 Client2。
3.3 版本
有了 Watcher 機制,就可以實現分散式協調/通知了,假設有這樣的場景,兩個客戶端同時對 B 進行寫入操作,這兩個客戶端就會存在競爭關係,通常需要對 B 進行加鎖操作,ZK 通過 version 版本號來控制實現樂觀鎖中的「寫入校驗」機制。
Zookeeper 的每個 ZNode 上都會存儲數據,對應於每個 ZNode,Zookeeper 都會為其維護一個叫作 Stat 的數據結構,Stat 中記錄了這個 ZNode 的三個數據版本,分別是 version(當前ZNode的版本)、cversion(當前ZNode 子節點的版本)和 aversion(當前ZNode的ACL版本)。
znode 里都有些啥呢?
3.4 Stat 結構體
Znodes 維護了一個 stat 結構,其中包含數據更改、ACL更改的版本號、時間戳等。
狀態屬性 | 說明 |
---|---|
czxid | 創建節點的事務zxid。 每次修改 ZK 狀態都會收到一個zxid形式的時間戳,也就是 ZK 事務ID。事務ID是 ZK 中所有修改總的次序。每個修改都有唯一的zxid,如果zxid1小於zxid2,那麼zxid1在zxid2之前發生 |
ctime | znode被創建的毫秒數(從1970年開始) |
mzxid | znode最後更新的事務zxid |
mtime | znode最後修改的毫秒數(從1970年開始) |
pzxid | znode最後更新的子節點zxid |
version | 數據節點版本號 |
cversion | 子節點版本號,znode子節點修改次數 |
aversion | znode訪問控制列表的變化號 |
ephemeralOwner | 如果是臨時節點,這個是znode擁有者的session id。如果不是臨時節點則是0 |
dataLength | znode的數據長度 |
numChildren | znode子節點數量 |
3.5 會話(Session)
Session 指的是 ZooKeeper 伺服器與客戶端會話。
在 ZooKeeper 中,一個客戶端連接是指客戶端和伺服器之間的一個 TCP 長連接。客戶端啟動的時候,首先會與伺服器建立一個 TCP 連接,從第一次連接建立開始,客戶端會話的生命周期也開始了。通過這個連接,客戶端能夠通過心跳檢測與伺服器保持有效的會話,也能夠向 Zookeeper 伺服器發送請求並接受響應,同時還能夠通過該連接接收來自伺服器的 Watch 事件通知。
Session 作為會話實體,用來代表客戶端會話,其包括 4 個屬性:
- SessionID,用來全局唯一識別會話;
- TimeOut,會話超時事件。客戶端在創造 Session 實例的時候,會設置一個會話超時的時間。當由於伺服器壓力太大、網路故障或是客戶端主動斷開連接等各種原因導致客戶端連接斷開時,只要在 sessionTimeout 規定的時間內能夠重新連接上集群中任意一台伺服器,那麼之前創建的會話仍然有效;
- TickTime,下次會話超時時間點;
- isClosing,當服務端如果檢測到會話超時失效了,會通過設置這個屬性將會話關閉。
3.6 ACL
Zookeeper 採用 ACL(Access Control Lists)策略來進行許可權控制,類似於 UNIX 文件系統的許可權控制。Zookeeper 定義了如下 5 種許可權:
- CREATE: 創建子節點的許可權
- READ: 獲取節點數據和子節點列表的許可權
- WRITE: 更新節點數據的許可權
- DELETE: 刪除子節點的許可權
- ADMIN: 設置節點ACL的許可權
其中尤其需要注意的是,CREATE 和 DELETE 這兩種許可權都是針對子節點的許可權控制。
3.7 集群角色
最典型集群模式:Master/Slave 模式(主備模式)。在這種模式中,通常 Master 伺服器作為主伺服器提供寫服務,其他的 Slave 從伺服器通過非同步複製的方式獲取 Master 伺服器最新的數據提供讀服務。
但是,在 ZooKeeper 中沒有選擇傳統的 Master/Slave 概念,而是引入了Leader、Follower 和 Observer 三種角色。
- Leader: 為客戶端提供讀和寫的服務,負責投票的發起和決議,更新系統狀態
- Follower: 為客戶端提供讀服務,如果是寫服務則轉發給 Leader。在選舉過程中參與投票
- Observer: 為客戶端提供讀伺服器,如果是寫服務則轉發給 Leader。不參與選舉過程中的投票,也不參與「過半寫成功」策略。在不影響寫性能的情況下提升集群的讀性能。此角色是在 zookeeper3.3 系列新增的角色。
server 狀態
- LOOKING:尋找Leader狀態
- LEADING:領導者狀態,表明當前伺服器角色是 Leader
- FOLLOWING:跟隨者狀態,表明當前伺服器角色是 Follower
- OBSERVING:觀察者狀態,表明當前伺服器角色是 Observer
選舉機制
- 伺服器1啟動,此時只有它一台伺服器啟動了,它發出去的報文沒有任何響應,所以它的選舉狀態一直是LOOKING 狀態。
- 伺服器2啟動,它與最開始啟動的伺服器1進行通訊,互相交換自己的選舉結果,由於兩者都沒有歷史數據,所以 id 值較大的伺服器2勝出,但是由於沒有達到超過半數以上的伺服器都同意選舉它(這個例子中的半數以上是3),所以伺服器1、2還是繼續保持 LOOKING 狀態。
- 伺服器3啟動,根據前面的理論分析,伺服器3成為伺服器1、2、3中的老大,而與上面不同的是,此時有三台伺服器選舉了它,所以它成為了這次選舉的Leader。
- 伺服器4啟動,根據前面的分析,理論上伺服器4應該是伺服器1、2、3、4中最大的,但是由於前面已經有半數以上的伺服器選舉了伺服器3,所以它只能接受當小弟的命了。
- 伺服器5啟動,同4一樣當小弟。
Watcher 監聽器
Zookeeper 中最有特色且最不容易理解的是監視(Watches)。
Zookeeper 所有的讀操作——getData(),getChildren(), 和 exists() 都可以設置監視(watch),監視事件可以理解為一次性的觸發器, 官方定義如下: a watch event is one-time trigger, sent to the client that set the watch, which occurs when the data for which the watch was set changes。對此需要作出如下理解:
-
One-time trigger(一次性觸發)
當設置監視的數據發生改變時,該監視事件會被發送到客戶端,例如,如果客戶端調用了
getData("/znode1", true)
並且稍後/znode1
節點上的數據發生了改變或者被刪除了,客戶端將會獲取到/znode1
發生變化的監視事件,而如果/znode1
再一次發生了變化,除非客戶端再次對/znode1
設置監視,否則客戶端不會收到事件通知。(3.6之後可以設置永久監視) -
Sent to the client(發送至客戶端)
Zookeeper 客戶端和服務端是通過 socket 進行通訊的,由於網路存在故障,所以監視事件很有可能不會成功到達客戶端,監視事件是非同步發送至監視者的,Zookeeper 本身提供了保序性(ordering guarantee):即客戶端只有首先看到了監視事件後,才會感知到它所設置監視的 znode 發生了變化(a client will never see a change for which it has set a watch until it first sees the watch event)。 網路延遲或者其他因素可能導致不同的客戶端在不同的時刻感知某一監視事件,但是不同的客戶端所看到的一切具有一致的順序。
-
The data for which the watch was set(被設置 watch 的數據)
這意味著 znode 節點本身具有不同的改變方式。你也可以想像 Zookeeper 維護了兩條監視鏈表:數據監視和子節點監視(data watches and child watches),
getData()
和exists()
設置數據監視,getChildren()
設置子節點監視。 或者,你也可以想像 Zookeeper 設置的不同監視返回不同的數據,getData()
和exists()
返回 znode 節點的相關資訊,而getChildren()
返回子節點列表。因此,setData()
會觸發設置在某一節點上所設置的數據監視(假定數據設置成功),而一次成功的create()
操作則會觸發當前節點上所設置的數據監視以及父節點的子節點監視。一次成功的delete()
操作將會觸發當前節點的數據監視和子節點監視事件,同時也會觸發該節點父節點的child watch
。
Zookeeper 中的監視是輕量級的,因此容易設置、維護和分發。當客戶端與 Zookeeper 伺服器端失去聯繫時,客戶端並不會收到監視事件的通知,只有當客戶端重新連接後,若在必要的情況下,以前註冊的監視會重新被註冊並觸發,對於開發人員來說這通常是透明的。只有一種情況會導致監視事件的丟失,即:通過 exists()
設置了某個 znode 節點的監視,但是如果某個客戶端在此 znode 節點被創建和刪除的時間間隔內與 zookeeper 伺服器失去了聯繫,該客戶端即使稍後重新連接 zookeepe r伺服器後也得不到事件通知。
從上圖可以看到,Watcher 機制包括三個角色:客戶端執行緒、客戶端的 WatchManager 以及 ZooKeeper 伺服器。Watcher 機制就是這三個角色之間的交互,整個過程分為註冊、存儲和通知三個步驟:
- 客戶端向 ZooKeeper 伺服器註冊一個 Watcher 監聽;
- 把這個監聽資訊存儲到客戶端的 WatchManager 中;
- 當 ZooKeeper 中的節點發生變化時,會通知客戶端,客戶端會調用相應 Watcher 對象中的回調方法。
文章持續更新,可以微信搜「 JavaKeeper 」第一時間閱讀,無套路領取 500+ 本電子書和 30+ 影片教學和源碼,本文 GitHub github.com/JavaKeeper 已經收錄,Javaer 開發、面試必備技能兵器譜,有你想要的。
參考:
《從Paxos到ZooKeeper 分散式一致性原理與實踐》
《阿里中間件團隊部落格》//jm.taobao.org/2011/10/08/1232/
《Zookeeper官方文檔》//zookeeper.apache.org/doc/
《尚矽谷Zookeeper》
//cloud.tencent.com/developer/article/1578401