本來想系統回顧下 ZooKeeper的,可是網上沒找到一篇合自己胃口的文章,寫的差不多的,感覺大部分都是基于《從Paxos到ZooKeeper 分布式一緻性原理與實踐》寫的,是以自己讀了一遍,加上項目中的使用,做個整理。加油,奧利給!

img
前言
面試常常被要求「熟悉分布式技術」,當年搞 “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 特性
圖檔來源:官網wiki
- ZooKeeper:一個上司者(leader),多個跟随者(follower)組成的叢集。
- Leader 負責進行投票的發起和決議,更新系統狀态。
- Follower 用于接收客戶請求并向用戶端傳回結果,在選舉 Leader 過程中參與投票。
- 叢集中隻要有半數以上節點存活,Zookeeper 叢集就能正常服務。
- 全局資料一緻(單一視圖):每個 Server 儲存一份相同的資料副本,Client 無論連接配接到哪個 Server,資料都是一緻的。
- 順序一緻性: 從同一用戶端發起的事務請求,最終将會嚴格地按照順序被應用到 ZooKeeper 中去。
- 原子性: 所有事務請求的處理結果在整個叢集中所有機器上的應用情況是一緻的,也就是說,要麼整個叢集中所有的機器都成功應用了某一個事務,要麼都沒有應用。
- 實時性,在一定時間範圍内,client 能讀到最新資料。
- 可靠性: 一旦一次更改請求被應用,更改的結果就會被持久化,直到被下一次更改覆寫。
1.5 設計目标
- 簡單的資料結構 :Zookeeper 使得分布式程式能夠通過一個共享的樹形結構的名字空間來進行互相協調,即Zookeeper 伺服器記憶體中的資料模型由一系列被稱為
的資料節點組成,Zookeeper 将全量的資料存儲在記憶體中,以此來提高伺服器吞吐、減少延遲的目的。ZNode
- 可以建構叢集 : 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
來指定)。ZK 的父節點(CreateMode.EPHEMERAL_SEQUENTIAL
)維持一份 sequence,保證子節點建立的時序性,進而也形成了每個用戶端的全局時序。/distribute_lock
個人感覺還是用 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 之間的最大響應時間機關,假如響應超過
,Leader 認為 Follwer 死掉,從伺服器清單中删除 Follwer;syncLimit * tickTime
- 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
選舉機制
zk-vote
- 伺服器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伺服器後也得不到事件通知。
圖檔來源:yht7
從上圖可以看到,Watcher 機制包括三個角色:用戶端線程、用戶端的 WatchManager 以及 ZooKeeper 伺服器。Watcher 機制就是這三個角色之間的互動,整個過程分為注冊、存儲和通知三個步驟:
- 用戶端向 ZooKeeper 伺服器注冊一個 Watcher 監聽;
- 把這個監聽資訊存儲到用戶端的 WatchManager 中;
- 當 ZooKeeper 中的節點發生變化時,會通知用戶端,用戶端會調用相應 Watcher 對象中的回調方法。
文章持續更新,可以微信搜「 JavaKeeper 」第一時間閱讀,無套路領取 500+ 本電子書和 30+ 視訊教學和源碼,本文 GitHub github.com/JavaKeeper 已經收錄,Javaer 開發、面試必備技能兵器譜,有你想要的。
參考:
《從Paxos到ZooKeeper 分布式一緻性原理與實踐》
《阿裡中間件團隊部落格》http://jm.taobao.org/2011/10/08/1232/
《Zookeeper官方文檔》https://zookeeper.apache.org/doc/
《尚矽谷Zookeeper》
https://cloud.tencent.com/developer/article/1578401