講解 Zookeeper 的五個核心知識點
1 ZooKeeper 簡介
ZooKeeper 是一個開源的分佈式協調框架,它的定位是爲分佈式應用提供一致性服務,是整個大數據體系的管理員。ZooKeeper 會封裝好複雜易出錯的關鍵服務,將高效、穩定、易用的服務提供給用戶使用。
如果上面的官方言語你不太理解,你可以認爲 ZooKeeper = 文件系統 + 監聽通知機制。
1.1 文件系統
Zookeeper 維護一個類似文件系統的樹狀數據結構,這種特性使得 Zookeeper 不能用於存放大量的數據,每個節點的存放數據上限爲 1M。每個子目錄項如 NameService 都被稱作爲 znode(目錄節點)。和文件系統一樣,我們能夠自由的增加、刪除 znode,在一個 znode 下增加、刪除子 znode,唯一的不同在於 znode 是可以存儲數據的。默認有四種類型的 znode:
-
持久化目錄節點 PERSISTENT:客戶端與 zookeeper 斷開連接後,該節點依舊存在。
-
持久化順序編號目錄節點 PERSISTENT_SEQUENTIAL:客戶端與 zookeeper 斷開連接後,該節點依舊存在,只是 Zookeeper 給該節點名稱進行順序編號。
-
臨時目錄節點 EPHEMERAL:客戶端與 zookeeper 斷開連接後,該節點被刪除。
-
臨時順序編號目錄節點 EPHEMERAL_SEQUENTIAL:客戶端與 zookeeper 斷開連接後,該節點被刪除,只是 Zookeeper 給該節點名稱進行順序編號。
1.2 監聽通知機制
Watcher 監聽機制是 Zookeeper 中非常重要的特性,我們基於 Zookeeper 上創建的節點,可以對這些節點綁定監聽事件,比如可以監聽節點數據變更、節點刪除、子節點狀態變更等事件,通過這個事件機制,可以基於 Zookeeper 實現分佈式鎖、集羣管理等功能。
Watcher 特性:
當數據發生變化的時候, Zookeeper 會產生一個 Watcher 事件,並且會發送到客戶端。但是客戶端只會收到一次通知。如果後續這個節點再次發生變化,那麼之前設置 Watcher 的客戶端不會再次收到消息。(Watcher 是一次性的操作)。可以通過循環監聽去達到永久監聽效果。
ZooKeeper 的 Watcher 機制,總的來說可以分爲三個過程:
客戶端註冊 Watcher,註冊 watcher 有 3 種方式,getData、exists、getChildren。
服務器處理 Watcher 。
客戶端回調 Watcher 客戶端。
首先要有一個 main() 線程
在 main 線程中創建 Zookeeper 客戶端,這時就會創建兩個線程,一個負責網絡連接通信(connet),一個負責監聽(listener)。
通過 connect 線程將註冊的監聽事件發送給 Zookeeper。
在 Zookeeper 的註冊監聽器列表中將註冊的監聽事件添加到列表中。
Zookeeper 監聽到有數據或路徑變化,就會將這個消息發送給 listener 線程。
listener 線程內部調用了 process() 方法。
1.3 Zookeeper 特點
-
集羣:Zookeeper 是一個領導者(Leader),多個跟隨者(Follower)組成的集羣。
-
高可用性:集羣中只要有半數以上節點存活,Zookeeper 集羣就能正常服務。
-
全局數據一致:每個 Server 保存一份相同的數據副本,Client 無論連接到哪個 Server,數據都是一致的。
-
更新請求順序進行:來自同一個 Client 的更新請求按其發送順序依次執行。
-
數據更新原子性:一次數據更新要麼成功,要麼失敗。
-
實時性:在一定時間範圍內,Client 能讀到最新數據。
-
從
設計模式
角度來看,zk 是一個基於觀察者設計模式的框架,它負責管理跟存儲大家都關心的數據,然後接受觀察者的註冊,數據反生變化 zk 會通知在 zk 上註冊的觀察者做出反應。 -
Zookeeper 是一個分佈式協調系統,滿足 CP 性,跟 SpringCloud 中的 Eureka 滿足 AP 不一樣。
分佈式協調系統:Leader 會同步數據到 follower,用戶請求可通過 follower 得到數據,這樣不會出現單點故障,並且只要同步時間無限短,那這就是個好的 分佈式協調系統。
CAP 原則又稱 CAP 定理,指的是在一個分佈式系統中,一致性(Consistency)、可用性(Availability)、分區容錯性(Partition tolerance)。CAP 原則指的是,這三個要素最多隻能同時實現兩點,不可能三者兼顧。
2 Zookeeper 提供的功能
通過對 Zookeeper 中豐富的數據節點進行交叉使用,配合 Watcher 事件通知機制,可以非常方便的構建一系列分佈式應用中涉及的核心功能,比如 數據發佈 / 訂閱、負載均衡、命名服務、分佈式協調 / 通知、集羣管理、Master 選舉、分佈式鎖和分佈式隊列 等功能。
1. 數據發佈 / 訂閱
當某些數據由幾個機器共享,且這些信息經常變化數據量還小的時候,這些數據就適合存儲到 ZK 中。
-
數據存儲:將數據存儲到 Zookeeper 上的一個數據節點。
-
數據獲取:應用在啓動初始化節點從 Zookeeper 數據節點讀取數據,並在該節點上註冊一個數據變更 Watcher
-
數據變更:當變更數據時會更新 Zookeeper 對應節點數據,Zookeeper 會將數據變更通知發到各客戶端,客戶端接到通知後重新讀取變更後的數據即可。
2. 分佈式鎖
關於分佈式鎖其實在 Redis 中已經講過了,並且 Redis 提供的分佈式鎖是比 ZK 性能強的。基於 ZooKeeper 的分佈式鎖一般有如下兩種。
- 保持獨佔
核心思想:在 zk 中有一個唯一的臨時節點,只有拿到節點的纔可以操作數據,沒拿到的線程就需要等待。缺點:可能引發
羊羣效應
,第一個用完後瞬間有 999 個同時併發的線程向 zk 請求獲得鎖。
- 控制時序
主要是避免了羊羣效應,臨時節點已經預先存在,所有想要獲得鎖的線程在它下面創建臨時順序編號目錄節點,編號最小的獲得鎖,用完刪除,後面的依次排隊獲取。
- 負載均衡
多個相同的 jar 包在不同的服務器上開啓相同的服務,可以通過 nginx 在服務端進行負載均衡的配置。也可以通過 ZooKeeper 在客戶端進行負載均衡配置。
-
多個服務註冊
-
客戶端獲取中間件地址集合
-
從集合中隨機選一個服務執行任務
ZooKeeper 負載均衡和 Nginx 負載均衡區別:
ZooKeeper 不存在單點問題,zab 機制保證單點故障可重新選舉一個 leader 只負責服務的註冊與發現,不負責轉發,減少一次數據交換(消費方與服務方直接通信),需要自己實現相應的負載均衡算法。
Nginx 存在單點問題,單點負載高數據量大, 需要通過 KeepAlived + LVS 備機實現高可用。每次負載,都充當一次中間人轉發角色,增加網絡負載量(消費方與服務方間接通信),自帶負載均衡算法。
4. 命名服務
命名服務是指通過指定的名字來獲取資源或者服務的地址,利用 zk 創建一個全局唯一的路徑,這個路徑就可以作爲一個名字,指向集羣中的集羣,提供的服務的地址,或者一個遠程的對象等等。
5. 分佈式協調 / 通知
-
對於系統調度來說,用戶更改 zk 某個節點的 value, ZooKeeper 會將這些變化發送給註冊了這個節點的 watcher 的所有客戶端,進行通知。
-
對於執行情況彙報來說,每個工作進程都在目錄下創建一個攜帶工作進度的臨時節點,那麼彙總的進程可以監控目錄子節點的變化獲得工作進度的實時的全局情況。
6. 集羣管理
大數據體系下的大部分集羣服務好像都通過 ZooKeeper 管理的,其實管理的時候主要關注的就是機器的動態上下線跟 Leader 選舉。
- 動態上下線:
比如在 zookeeper 服務器端有一個 znode 叫 /Configuration,那麼集羣中每一個機器啓動的時候都去這個節點下創建一個 EPHEMERAL 類型的節點,比如 server1 創建 /Configuration/Server1,server2 創建 /Configuration /Server1,然後 Server1 和 Server2 都 watch /Configuration 這個父節點,那麼也就是這個父節點下數據或者子節點變化都會通知到該節點進行 watch 的客戶端。
- Leader 選舉:
利用 ZooKeeper 的強一致性,能夠保證在分佈式高併發情況下節點創建的全局唯一性,即:同時有多個客戶端請求創建 /Master 節點,最終一定只有一個客戶端請求能夠創建成功。利用這個特性,就能很輕易的在分佈式環境中進行集羣選舉了。
就是動態 Master 選舉。這就要用到 EPHEMERAL_SEQUENTIAL 類型節點的特性了,這樣每個節點會
自動被編號
。允許所有請求都能夠創建成功,但是得有個創建順序,每次選取序列號最小的那個機器作爲 Master 。
3 Leader 選舉
ZooKeeper 集羣節點個數一定是奇數個,一般 3 個或者 5 個就 OK。爲避免集羣羣龍無首,一定要選個大哥出來當 Leader。這是個高頻考點。
3.1 預備知識
3.1.1. 節點四種狀態。
-
LOOKING:尋 找 Leader 狀態。當服務器處於該狀態時會認爲當前集羣中沒有 Leader,因此需要進入 Leader 選舉狀態。
-
FOLLOWING:跟隨者狀態。處理客戶端的非事務請求,轉發事務請求給 Leader 服務器,參與事務請求 Proposal(提議) 的投票,參與 Leader 選舉投票。
-
LEADING:領導者狀態。事務請求的唯一調度和處理者,保證集羣事務處理的順序性,集羣內部個服務器的調度者 (管理 follower, 數據同步)。
-
OBSERVING:觀察者狀態。3.0 版本以後引入的一個服務器角色,在不影響集羣事務處理能力的基礎上提升集羣的非事務處理能力,處理客戶端的非事務請求,轉發事務請求給 Leader 服務器,不參與任何形式的投票。
3.1.2 服務器 ID
既 Server id,一般在搭建 ZK 集羣時會在 myid 文件中給每個節點搞個唯一編號,編號越大在Leader選擇算法中的權重越大
,比如初始化啓動時就是根據服務器 ID 進行比較。
3.1.3 ZXID
ZooKeeper 採用全局遞增的事務 Id 來標識,所有 proposal(提議) 在被提出的時候加上了 ZooKeeper Transaction Id ,zxid 是 64 位的 Long 類型,這是保證事務的順序一致性的關鍵。zxid 中高 32 位表示紀元 epoch,低 32 位表示事務標識 xid。你可以認爲 zxid 越大說明存儲數據越新。
-
每個 leader 都會具有不同的 epoch 值,表示一個紀元 / 朝代,用來標識 leader 週期。每個新的選舉開啓時都會生成一個新的 epoch,新的 leader 產生的話 epoch 會自增,會將該值更新到所有的 zkServer 的 zxid 和 epoch,
-
xid 是一個依次遞增的事務編號。數值越大說明數據越新,所有 proposal(提議)在被提出的時候加上了 zxid,然後會依據數據庫的兩階段過程,首先會向其他的 server 發出事務執行請求,如果超過半數的機器都能執行並且能夠成功,那麼就會開始執行。
3.2 Leader 選舉
Leader 的選舉一般分爲啓動時選舉跟 Leader 掛掉後的運行時選舉。
3.2.1 啓動時 Leader 選舉
- 服務器 1 啓動,發起一次選舉。
服務器 1 投自己一票。此時服務器 1 票數一票,不夠半數以上(3 票),選舉無法完成,服務器 1 狀態保持爲 LOOKING。
- 服務器 2 啓動,再發起一次選舉。
服務器 1 和 2 分別投自己一票,此時服務器 1 發現服務器 2 的 id 比自己大,更改選票投給服務器 2。此時服務器 1 票數 0 票,服務器 2 票數 2 票,不夠半數以上(3 票),選舉無法完成。服務器 1,2 狀態保持 LOOKING。
- 服務器 3 啓動,發起一次選舉。
與上面過程一樣,服務器 1 和 2 先投自己一票,然後因爲服務器 3id 最大,兩者更改選票投給爲服務器 3。此次投票結果:服務器 1 爲 0 票,服務器 2 爲 0 票,服務器 3 爲 3 票。此時服務器 3 的票數已經超過半數(3 票),服務器 3 當選 Leader。服務器 1,2 更改狀態爲 FOLLOWING,服務器 3 更改狀態爲 LEADING;
- 服務器 4 啓動,發起一次選舉。
此時服務器 1、2、3 已經不是 LOOKING 狀態,不會更改選票信息,交換選票信息結果。服務器 3 爲 3 票,服務器 4 爲 1 票。此時服務器 4 服從多數,更改選票信息爲服務器 3,服務器 4 並更改狀態爲 FOLLOWING。
- 服務器 5 啓動,發起一次選舉
同 4 一樣投票給 3,此時服務器 3 一共 5 票,服務器 5 爲 0 票。服務器 5 並更改狀態爲 FOLLOWING;
- 最終
Leader 是服務器 3,狀態爲 LEADING。其餘服務器是 Follower,狀態爲 FOLLOWING。
3.2.2 運行時 Leader 選舉
運行時候如果 Master 節點崩潰了會走恢復模式,新 Leader 選出前會暫停對外服務,大致可以分爲四個階段 選舉
、發現
、同步
、廣播
。
-
每個 Server 會發出一個投票,第一次都是投自己,其中投票信息 = (myid,ZXID)
-
收集來自各個服務器的投票
-
處理投票並重新投票,處理邏輯:優先比較 ZXID,然後比較 myid。
-
統計投票,只要超過半數的機器接收到同樣的投票信息,就可以確定 leader,注意 epoch 的增加跟同步。
-
改變服務器狀態 Looking 變爲 Following 或 Leading。
-
當 Follower 鏈接上 Leader 之後,Leader 服務器會根據自己服務器上最後被提交的 ZXID 和 Follower 上的 ZXID 進行比對,比對結果要麼回滾,要麼和 Leader 同步,保證集羣中各個節點的事務一致。
-
集羣恢復到廣播模式,開始接受客戶端的寫請求。
3.3 腦裂
腦裂問題是集羣部署必須考慮的一點,比如在 Hadoop 跟 Spark 集羣中。而 ZAB 爲解決腦裂問題,要求集羣內的節點數量爲 2N+1。當網絡分裂後,始終有一個集羣的節點數量過半數,而另一個節點數量小於 N+1, 因爲選舉 Leader 需要過半數的節點同意,所以我們可以得出如下結論:
有了過半機制,對於一個 Zookeeper 集羣,要麼沒有 Leader,要沒只有 1 個 Leader,這樣就避免了腦裂問題
4 一致性協議之 ZAB
建議先看下 淺談大數據中的 2PC、3PC、Paxos、Raft、ZAB ,不然可能看的喫力。
4.1 ZAB 協議介紹
ZAB (Zookeeper Atomic Broadcast 原子廣播協議) 協議是爲分佈式協調服務 ZooKeeper 專門設計的一種支持崩潰恢復的一致性協議。基於該協議,ZooKeeper 實現了一種主從模式的系統架構來保持集羣中各個副本之間的數據一致性。
分佈式系統中 leader 負責外部客戶端的寫請求。follower 服務器負責讀跟同步。這時需要解決倆問題。
-
Leader 服務器是如何把數據更新到所有的 Follower 的。
-
Leader 服務器突然間失效了,集羣咋辦?
因此 ZAB 協議爲了解決上面兩個問題而設計了兩種工作模式,整個 Zookeeper 就是在這兩個模式之間切換:
-
原子廣播模式:把數據更新到所有的 follower。
-
崩潰恢復模式:Leader 發生崩潰時,如何恢復。
4.2 原子廣播模式
你可以認爲消息廣播機制是簡化版的 2PC 協議,就是通過如下的機制保證事務的順序一致性的。
-
leader 從客戶端收到一個寫請求後生成一個新的事務併爲這個事務生成一個唯一的
ZXID
, -
leader 將將帶有 zxid 的消息作爲一個提案 (proposal) 分發給所有 FIFO 隊列。
-
FIFO 隊列取出隊頭 proposal 給 follower 節點。
-
當 follower 接收到 proposal,先將 proposal 寫到硬盤,寫硬盤成功後再向 leader 回一個 ACK。
-
FIFO 隊列把 ACK 返回給 Leader。
-
當 leader 收到超過一半以上的 follower 的 ack 消息,leader 會進行 commit 請求,然後再給 FIFO 發送 commit 請求。
-
當 follower 收到 commit 請求時,會判斷該事務的 ZXID 是不是比歷史隊列中的任何事務的 ZXID 都小,如果是則提交,如果不是則等待比它更小的事務的 commit(保證順序性)
4.3 崩潰恢復
消息廣播過程中,Leader 崩潰了還能保證數據一致嗎?當 Leader 崩潰會進入崩潰恢復模式。其實主要是對如下兩種情況的處理。
-
Leader 在複製數據給所有 Follwer 之後崩潰,咋搞?
-
Leader 在收到 Ack 並提交了自己,同時發送了部分 commit 出去之後崩潰咋辦?
針對此問題,ZAB 定義了 2 個原則:
-
ZAB 協議確保
執行
那些已經在 Leader 提交的事務最終會被所有服務器提交。 -
ZAB 協議確保
丟棄
那些只在 Leader 提出 / 複製,但沒有提交的事務。
至於如何實現確保提交已經被 Leader 提交的事務,同時丟棄已經被跳過的事務呢?關鍵點就是依賴上面說到過的 ZXID 了。
4.4 ZAB 特性
- 一致性保證
可靠提交 (Reliable delivery) :如果一個事務 A 被一個 server 提交(committed) 了,那麼它最終一定會被所有的 server 提交
- 全局有序 (Total order)
假設有 A、B 兩個事務,有一臺 server 先執行 A 再執行 B,那麼可以保證所有 server 上 A 始終都被在 B 之前執行
- 因果有序 (Causal order)
如果發送者在事務 A 提交之後再發送 B, 那麼 B 必將在 A 之後執行
- 高可用性
只要大多數(法定數量)節點啓動,系統就行正常運行
- 可恢復性
當節點下線後重啓,它必須保證能恢復到當前正在執行的事務
4.5 ZAB 和 Paxos 對比
相同點:
兩者都存在一個類似於 Leader 進程的角色,由其負責協調多個 Follower 進程的運行.
Leader 進程都會等待超過半數的 Follower 做出正確的反饋後,纔會將一個提案進行提交.
ZAB 協議中,每個 Proposal 中都包含一個 epoch 值來代表當前的 Leader 週期,Paxos 中名字爲 Ballot
不同點:
ZAB 用來構建高可用的分佈式數據主備系統(Zookeeper),Paxos 是用來構建分佈式一致性狀態機系統。
5 ZooKeeper 零散知識
5.1 常見指令
Zookeeper 有三種部署模式:
單機部署:一臺機器上運行。
集羣部署:多臺機器運行。
僞集羣部署:一臺機器啓動多個 Zookeeper 實例運行。
部署完畢後常見指令如下:
| 命令基本語法 | 功能描述 |
| --- | --- |
| help | 顯示所有操作命令 |
| ls path [watch] | 顯示所有操作命令 |
| ls path [watch] | 查看當前節點數據並能看到更新次數等數據 |
| create | 普通創建, -s 含有序列,
-e 臨時(重啓或者超時消失) |
| get path [watch] | 獲得節點的值 |
| set | 設置節點的具體值 |
| stat | 查看節點狀態 |
| delete | 刪除節點 |
| rmr | 遞歸刪除節點 |
5.2 Zookeeper 客戶端
5.2.1. Zookeeper 原生客戶端
Zookeeper 客戶端是異步的哦!需要引入 CountDownLatch 來確保連接好了再做下面操作。Zookeeper 原生 api 是不支持迭代式的創建跟刪除路徑的,具有如下弊端。
會話的連接是異步的;必須用到回調函數 。
Watch 需要重複註冊:看一次 watch 註冊一次 。
Session 重連機制:有時 session 斷開還需要重連接。
開發複雜性較高:開發相對來說比較瑣碎。
5.2.2. ZkClient
開源的 zk 客戶端,在原生 API 基礎上封裝,是一個更易於使用的 zookeeper 客戶端,做了如下優化。
優化一 、在 session loss 和 session expire 時自動創建新的 ZooKeeper 實例進行重連。優化二、 將一次性 watcher 包裝爲持久 watcher。
5.2.3. Curator
開源的 zk 客戶端,在原生 API 基礎上封裝,apache 頂級項目。是 Netflix 公司開源的一套 Zookeeper 客戶端框架。瞭解過 Zookeeper 原生 API 都會清楚其複雜度。Curator 幫助我們在其基礎上進行封裝、實現一些開發細節,包括接連重連、反覆註冊 Watcher 和 NodeExistsException 等。目前已經作爲 Apache 的頂級項目出現,是最流行的 Zookeeper 客戶端之一。
5.2.4. Zookeeper 圖形化客戶端工具
工具名叫 ZooInspector,百度安裝教程即可。
5.3 ACL 權限控制機制
ACL 全稱爲 Access Control List 即訪問控制列表,用於控制資源的訪問權限。zookeeper 利用 ACL 策略控制節點的訪問權限,如節點數據讀寫、節點創建、節點刪除、讀取子節點列表、設置節點權限等。
5.4 Zookeeper 使用注意事項
-
集羣中機器的數量並不是越多越好,一個寫操作需要半數以上的節點 ack,所以集羣節點數越多,整個集羣可以抗掛點的節點數越多 (越可靠),但是吞吐量越差。集羣的數量必須爲奇數。
-
zk 是基於內存進行讀寫操作的,有時候會進行消息廣播,因此不建議在節點存取容量比較大的數據。
-
dataDir 目錄、dataLogDir 兩個目錄會隨着時間推移變得龐大,容易造成硬盤滿了。建議自己編寫或使用自帶的腳本保留最新的 n 個文件。
-
默認最大連接數 默認爲 60,配置 maxClientCnxns 參數,配置單個客戶端機器創建的最大連接數。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/W6QgmFTpXQ8EL-dVvLWsyg