前言
面試常常被要求「熟悉分佈式技術」,當年搞 “XXX管理系統” 的時候,我都不知道分佈式系統是個啥。分佈式系統是一個硬件或軟件組件分佈在不同的網絡計算機中上,彼此之間僅僅通過消息傳遞進行通信和協調的系統。
計算機系統從集中式到分佈式的變革伴隨着包括分佈式網絡、分佈式事務、分佈式數據一致性等在內的一系列問題和挑戰,同時也催生了一大批諸如ACID
、CAP
和 BASE
等經典理論的快速發展。
爲了解決分佈式一致性問題,湧現出了一大批經典的一致性協議和算法,最爲著名的就是二階段提交協議(2PC),三階段提交協議(3PC)和Paxos
算法。Zookeeper
的一致性是通過基於 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 服務器內存中的數據模型由一系列被稱爲
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 狀態監控和選舉。(TODO:圖應該是註冊和Register and watch)
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
選舉機制
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 對象中的回調方法。
參考:
《從Paxos到ZooKeeper 分佈式一致性原理與實踐》
《阿里中間件團隊博客》http://jm.taobao.org/2011/10/08/1232/
《Zookeeper官方文檔》https://zookeeper.apache.org/doc/
《尚硅谷Zookeeper》
https://cloud.tencent.com/developer/article/1578401
有道無術,術可成;有術無道,止於術
歡迎大家關注Java之道公衆號
好文章,我在看❤️