深刻淺出 ZooKeeper

ZooKeeper 是一個分佈式協調服務 ,由 Apache 進行維護。

ZooKeeper 能夠視爲一個高可用的文件系統。html

ZooKeeper 能夠用於發佈/訂閱、負載均衡、命令服務、分佈式協調/通知、集羣管理、Master 選舉、分佈式鎖和分佈式隊列等功能 。node

1、ZooKeeper 簡介

1.1 ZooKeeper 是什麼

ZooKeeper 是 Apache 的頂級項目。ZooKeeper 爲分佈式應用提供了高效且可靠的分佈式協調服務,提供了諸如統一命名服務、配置管理和分佈式鎖等分佈式的基礎服務。在解決分佈式數據一致性方面,ZooKeeper 並無直接採用 Paxos 算法,而是採用了名爲 ZAB 的一致性協議。git

ZooKeeper 主要用來解決分佈式集羣中應用系統的一致性問題,它能提供基於相似於文件系統的目錄節點樹方式的數據存儲。可是 ZooKeeper 並非用來專門存儲數據的,它的做用主要是用來維護和監控存儲數據的狀態變化。經過監控這些數據狀態的變化,從而能夠達到基於數據的集羣管理。github

不少大名鼎鼎的框架都基於 ZooKeeper 來實現分佈式高可用,如:Dubbo、Kafka 等。算法

1.2 ZooKeeper 的特性

ZooKeeper 具備如下特性:數據庫

  • 順序一致性:全部客戶端看到的服務端數據模型都是一致的;從一個客戶端發起的事務請求,最終都會嚴格按照其發起順序被應用到 ZooKeeper 中。具體的實現可見下文:原子廣播。
  • 原子性:全部事務請求的處理結果在整個集羣中全部機器上的應用狀況是一致的,即整個集羣要麼都成功應用了某個事務,要麼都沒有應用。實現方式可見下文:事務。
  • 單一視圖:不管客戶端鏈接的是哪一個 Zookeeper 服務器,其看到的服務端數據模型都是一致的。
  • 高性能:ZooKeeper 將數據全量存儲在內存中,因此其性能很高。須要注意的是:因爲 ZooKeeper 的全部更新和刪除都是基於事務的,所以 ZooKeeper 在讀多寫少的應用場景中有性能表現較好,若是寫操做頻繁,性能會大大下滑。
  • 高可用:ZooKeeper 的高可用是基於副本機制實現的,此外 ZooKeeper 支持故障恢復,可見下文:選舉 Leader。

1.3 ZooKeeper 的設計目標

  • 簡單的數據模型
  • 能夠構建集羣
  • 順序訪問
  • 高性能

2、ZooKeeper 核心概念

2.1  數據模型

ZooKeeper 的數據模型是一個樹形結構的文件系統。apache

樹中的節點被稱爲 znode,其中根節點爲 /,每一個節點上都會保存本身的數據和節點信息。znode 能夠用於存儲數據,而且有一個與之相關聯的 ACL(詳情可見 ACL)。ZooKeeper 的設計目標是實現協調服務,而不是真的做爲一個文件存儲,所以 znode 存儲數據的大小被限制在 1MB 之內。服務器

ZooKeeper 的數據訪問具備原子性。其讀寫操做都是要麼所有成功,要麼所有失敗。session

znode 經過路徑被引用。znode 節點路徑必須是絕對路徑。數據結構

znode 有兩種類型:

  • 臨時的( EPHEMERAL ):客戶端會話結束時,ZooKeeper 就會刪除臨時的 znode。
  • 持久的(PERSISTENT ):除非客戶端主動執行刪除操做,不然 ZooKeeper 不會刪除持久的 znode。

2.2  節點信息

znode 上有一個順序標誌( SEQUENTIAL )。若是在建立 znode 時,設置了順序標誌( SEQUENTIAL ),那麼 ZooKeeper 會使用計數器爲 znode 添加一個單調遞增的數值,即 zxid。ZooKeeper 正是利用 zxid 實現了嚴格的順序訪問控制能力。

每一個 znode 節點在存儲數據的同時,都會維護一個叫作 Stat 的數據結構,裏面存儲了關於該節點的所有狀態信息。以下:

2.3 集羣角色

Zookeeper 集羣是一個基於主從複製的高可用集羣,每一個服務器承擔以下三種角色中的一種。

  • Leader:它負責 發起並維護與各 Follwer 及 Observer 間的心跳。全部的寫操做必需要經過 Leader 完成再由 Leader 將寫操做廣播給其它服務器。一個 Zookeeper 集羣同一時間只會有一個實際工做的 Leader。
  • Follower:它會響應 Leader 的心跳。Follower 可直接處理並返回客戶端的讀請求,同時會將寫請求轉發給 Leader 處理,而且負責在 Leader 處理寫請求時對請求進行投票。一個 Zookeeper 集羣可能同時存在多個 Follower。
  • Observer:角色與 Follower 相似,可是無投票權。

2.4 ACL

ZooKeeper 採用 ACL(Access Control Lists)策略來進行權限控制。

每一個 znode 建立時都會帶有一個 ACL 列表,用於決定誰能夠對它執行何種操做。

ACL 依賴於 ZooKeeper 的客戶端認證機制。ZooKeeper 提供瞭如下幾種認證方式:

  • digest:用戶名和密碼 來識別客戶端
  • sasl:經過 kerberos 來識別客戶端
  • ip:經過 IP 來識別客戶端

ZooKeeper 定義了以下五種權限:

  • CREATE:容許建立子節點;
  • READ:容許從節點獲取數據並列出其子節點;
  • WRITE:容許爲節點設置數據;
  • DELETE:容許刪除子節點;
  • ADMIN:容許爲節點設置權限。

3、ZooKeeper 工做原理

3.1 讀操做

Leader/Follower/Observer 均可直接處理讀請求,從本地內存中讀取數據並返回給客戶端便可。

因爲處理讀請求不須要服務器之間的交互,Follower/Observer 越多,總體系統的讀請求吞吐量越大,也即讀性能越好。

3.2 寫操做

全部的寫請求實際上都要交給 Leader 處理。Leader 將寫請求以事務形式發給全部 Follower 並等待 ACK,一旦收到半數以上 Follower 的 ACK,即認爲寫操做成功。

3.2.1 寫 Leader

由上圖可見,經過 Leader 進行寫操做,主要分爲五步:

  1. 客戶端向 Leader 發起寫請求。
  2. Leader 將寫請求以事務 Proposal 的形式發給全部 Follower 並等待 ACK。
  3. Follower 收到 Leader 的事務 Proposal 後返回 ACK。
  4. Leader 獲得過半數的 ACK(Leader 對本身默認有一個 ACK)後向全部的 Follower 和 Observer 發送 Commmit。
  5. Leader 將處理結果返回給客戶端。

注意

  • Leader 不須要獲得 Observer 的 ACK,即 Observer 無投票權。
  • Leader 不須要獲得全部 Follower 的 ACK,只要收到過半的 ACK 便可,同時 Leader 自己對本身有一個 ACK。上圖中有 4 個 Follower,只需其中兩個返回 ACK 便可,由於 $$(2+1) / (4+1) > 1/2$$ 。
  • Observer 雖然無投票權,但仍須同步 Leader 的數據從而在處理讀請求時能夠返回儘量新的數據。

3.2.2 寫 Follower/Observer

Follower/Observer 都可接受寫請求,但不能直接處理,而須要將寫請求轉發給 Leader 處理。

除了多了一步請求轉發,其它流程與直接寫 Leader 無任何區別。

3.3 事務

對於來自客戶端的每一個更新請求,ZooKeeper 具有嚴格的順序訪問控制能力。

爲了保證事務的順序一致性,ZooKeeper 採用了遞增的事務 id 號(zxid)來標識事務。

Leader 服務會爲每個 Follower 服務器分配一個單獨的隊列,而後將事務 Proposal 依次放入隊列中,並根據 FIFO(先進先出) 的策略進行消息發送。Follower 服務在接收到 Proposal 後,會將其以事務日誌的形式寫入本地磁盤中,並在寫入成功後反饋給 Leader 一個 Ack 響應。當 Leader 接收到超過半數 Follower 的 Ack 響應後,就會廣播一個 Commit 消息給全部的 Follower 以通知其進行事務提交,以後 Leader 自身也會完成對事務的提交。而每個 Follower 則在接收到 Commit 消息後,完成事務的提交。

全部的提議(proposal)都在被提出的時候加上了 zxid。zxid 是一個 64 位的數字,它的高 32 位是 epoch 用來標識 Leader 關係是否改變,每次一個 Leader 被選出來,它都會有一個新的 epoch,標識當前屬於那個 leader 的統治時期。低 32 位用於遞增計數。

詳細過程以下:

  • Leader 等待 Server 鏈接;
  • Follower 鏈接 Leader,將最大的 zxid 發送給 Leader;
  • Leader 根據 Follower 的 zxid 肯定同步點;
  • 完成同步後通知 follower 已經成爲 uptodate 狀態;
  • Follower 收到 uptodate 消息後,又能夠從新接受 client 的請求進行服務了。

3.4 觀察

客戶端註冊監聽它關心的 znode,當 znode 狀態發生變化(數據變化、子節點增減變化)時,ZooKeeper 服務會通知客戶端。

客戶端和服務端保持鏈接通常有兩種形式:

  • 客戶端向服務端不斷輪詢
  • 服務端向客戶端推送狀態

Zookeeper 的選擇是服務端主動推送狀態,也就是觀察機制( Watch )。

ZooKeeper 的觀察機制容許用戶在指定節點上針對感興趣的事件註冊監聽,當事件發生時,監聽器會被觸發,並將事件信息推送到客戶端。

客戶端使用 getData 等接口獲取 znode 狀態時傳入了一個用於處理節點變動的回調,那麼服務端就會主動向客戶端推送節點的變動:

從這個方法中傳入的 Watcher 對象實現了相應的 process 方法,每次對應節點出現了狀態的改變,WatchManager 都會經過如下的方式調用傳入 Watcher 的方法:

Set<Watcher> triggerWatch(String path, EventType type, Set<Watcher> supress) {
    WatchedEvent e = new WatchedEvent(type, KeeperState.SyncConnected, path);
    Set<Watcher> watchers;
    synchronized (this) {
        watchers = watchTable.remove(path);
    }
    for (Watcher w : watchers) {
        w.process(e);
    }
    return

Zookeeper 中的全部數據其實都是由一個名爲 DataTree 的數據結構管理的,全部的讀寫數據的請求最終都會改變這顆樹的內容,在發出讀請求時可能會傳入 Watcher 註冊一個回調函數,而寫請求就可能會觸發相應的回調,由 WatchManager 通知客戶端數據的變化。

通知機制的實現其實仍是比較簡單的,經過讀請求設置 Watcher 監聽事件,寫請求在觸發事件時就能將通知發送給指定的客戶端。

3.5 會話

ZooKeeper 客戶端經過 TCP 長鏈接鏈接到 ZooKeeper 服務集羣。會話 (Session) 從第一次鏈接開始就已經創建,以後經過心跳檢測機制來保持有效的會話狀態。經過這個鏈接,客戶端能夠發送請求並接收響應,同時也能夠接收到 Watch 事件的通知。

每一個 ZooKeeper 客戶端配置中都配置了 ZooKeeper 服務器集羣列表。啓動時,客戶端會遍歷列表去嘗試創建鏈接。若是失敗,它會嘗試鏈接下一個服務器,依次類推。

一旦一臺客戶端與一臺服務器創建鏈接,這臺服務器會爲這個客戶端建立一個新的會話。每一個會話都會有一個超時時間,若服務器在超時時間內沒有收到任何請求,則相應會話被視爲過時。一旦會話過時,就沒法再從新打開,且任何與該會話相關的臨時 znode 都會被刪除。

一般來講,會話應該長期存在,而這須要由客戶端來保證。客戶端能夠經過心跳方式(ping)來保持會話不過時。

ZooKeeper 的會話具備四個屬性:

  • sessionID:會話 ID,惟一標識一個會話,每次客戶端建立新的會話時,Zookeeper 都會爲其分配一個全局惟一的 sessionID。
  • TimeOut:會話超時時間,客戶端在構造 Zookeeper 實例時,會配置 sessionTimeout 參數用於指定會話的超時時間,Zookeeper 客戶端向服務端發送這個超時時間後,服務端會根據本身的超時時間限制最終肯定會話的超時時間。
  • TickTime:下次會話超時時間點,爲了便於 Zookeeper 對會話實行」分桶策略」管理,同時爲了高效低耗地實現會話的超時檢查與清理,Zookeeper 會爲每一個會話標記一個下次會話超時時間點,其值大體等於當前時間加上 TimeOut。
  • isClosing:標記一個會話是否已經被關閉,當服務端檢測到會話已經超時失效時,會將該會話的 isClosing 標記爲」已關閉」,這樣就能確保再也不處理來自該會話的新請求了。

Zookeeper 的會話管理主要是經過 SessionTracker 來負責,其採用了分桶策略(將相似的會話放在同一區塊中進行管理)進行管理,以便 Zookeeper 對會話進行不一樣區塊的隔離處理以及同一區塊的統一處理。

4、ZAB 協議

ZooKeeper 並無直接採用 Paxos 算法,而是採用了名爲 ZAB 的一致性協議。ZAB 協議不是 Paxos 算法,只是比較相似,兩者在操做上並不相同。

ZAB 協議是 Zookeeper 專門設計的一種支持崩潰恢復的原子廣播協議。

ZAB 協議是 ZooKeeper 的數據一致性和高可用解決方案。

ZAB 協議定義了兩個能夠無限循環的流程:

  • 選舉 Leader:用於故障恢復,從而保證高可用。
  • 原子廣播:用於主從同步,從而保證數據一致性。

4.1 選舉 Leader

ZooKeeper 的故障恢復

ZooKeeper 集羣採用一主(稱爲 Leader)多從(稱爲 Follower)模式,主從節點經過副本機制保證數據一致。

  • 若是 Follower 節點掛了 - ZooKeeper 集羣中的每一個節點都會單獨在內存中維護自身的狀態,而且各節點之間都保持着通信,只要集羣中有半數機器可以正常工做,那麼整個集羣就能夠正常提供服務。
  • 若是 Leader 節點掛了 - 若是 Leader 節點掛了,系統就不能正常工做了。此時,須要經過 ZAB 協議的選舉 Leader 機制來進行故障恢復。

ZAB 協議的選舉 Leader 機制簡單來講,就是:基於過半選舉機制產生新的 Leader,以後其餘機器將重新的 Leader 上同步狀態,當有過半機器完成狀態同步後,就退出選舉 Leader 模式,進入原子廣播模式。

4.1.1 術語

myid:每一個 Zookeeper 服務器,都須要在數據文件夾下建立一個名爲 myid 的文件,該文件包含整個 Zookeeper 集羣惟一的 ID(整數)。

zxid:相似於 RDBMS 中的事務 ID,用於標識一次更新操做的 Proposal ID。爲了保證順序性,該 zkid 必須單調遞增。所以 Zookeeper 使用一個 64 位的數來表示,高 32 位是 Leader 的 epoch,從 1 開始,每次選出新的 Leader,epoch 加一。低 32 位爲該 epoch 內的序號,每次 epoch 變化,都將低 32 位的序號重置。這樣保證了 zkid 的全局遞增性。

4.1.2 服務器狀態

  • LOOKING:不肯定 Leader 狀態。該狀態下的服務器認爲當前集羣中沒有 Leader,會發起 Leader 選舉。
  • FOLLOWING:跟隨者狀態。代表當前服務器角色是 Follower,而且它知道 Leader 是誰。
  • LEADING:領導者狀態。代表當前服務器角色是 Leader,它會維護與 Follower 間的心跳。
  • OBSERVING:觀察者狀態。代表當前服務器角色是 Observer,與 Folower 惟一的不一樣在於不參與選舉,也不參與集羣寫操做時的投票。

4.1.3 選票數據結構

每一個服務器在進行領導選舉時,會發送以下關鍵信息:

  • logicClock:每一個服務器會維護一個自增的整數,名爲 logicClock,它表示這是該服務器發起的第多少輪投票。
  • state:當前服務器的狀態。
  • self_id:當前服務器的 myid。
  • self_zxid:當前服務器上所保存的數據的最大 zxid。
  • vote_id:被推舉的服務器的 myid。
  • vote_zxid:被推舉的服務器上所保存的數據的最大 zxid。

4.1.4 投票流程

(1)自增選舉輪次

Zookeeper 規定全部有效的投票都必須在同一輪次中。每一個服務器在開始新一輪投票時,會先對本身維護的 logicClock 進行自增操做。

(2)初始化選票

每一個服務器在廣播本身的選票前,會將本身的投票箱清空。該投票箱記錄了所收到的選票。例:服務器 2 投票給服務器 3,服務器 3 投票給服務器 1,則服務器 1 的投票箱爲(2, 3), (3, 1), (1, 1)。票箱中只會記錄每一投票者的最後一票,如投票者更新本身的選票,則其它服務器收到該新選票後會在本身票箱中更新該服務器的選票。

(3)發送初始化選票

每一個服務器最開始都是經過廣播把票投給本身。

(4)接收外部投票

服務器會嘗試從其它服務器獲取投票,並記入本身的投票箱內。若是沒法獲取任何外部投票,則會確認本身是否與集羣中其它服務器保持着有效鏈接。若是是,則再次發送本身的投票;若是否,則立刻與之創建鏈接。

(5)判斷選舉輪次

收到外部投票後,首先會根據投票信息中所包含的 logicClock 來進行不一樣處理:

  • 外部投票的 logicClock大於本身的 logicClock。說明該服務器的選舉輪次落後於其它服務器的選舉輪次,當即清空本身的投票箱並將本身的 logicClock 更新爲收到的 logicClock,而後再對比本身以前的投票與收到的投票以肯定是否須要變動本身的投票,最終再次將本身的投票廣播出去。
  • 外部投票的 logicClock小於本身的 logicClock。當前服務器直接忽略該投票,繼續處理下一個投票。
  • 外部投票的 logickClock 與本身的相等。當時進行選票 PK。

(6)選票 PK

選票 PK 是基於(self\_id, self\_zxid)與(vote\_id, vote\_zxid)的對比:

  • 外部投票的 logicClock大於本身的 logicClock,則將本身的 logicClock 及本身的選票的 logicClock 變動爲收到的 logicClock。
  • logicClock一致,則對比兩者的 vote\_zxid,若外部投票的 vote\_zxid 比較大,則將本身的票中的 vote\_zxid 與 vote\_myid 更新爲收到的票中的 vote\_zxid 與 vote\_myid 並廣播出去,另外將收到的票及本身更新後的票放入本身的票箱。若是票箱內已存在(self\_myid, self\_zxid)相同的選票,則直接覆蓋。
  • 若兩者vote_zxid一致,則比較兩者的 vote\_myid,若外部投票的 vote\_myid 比較大,則將本身的票中的 vote\_myid 更新爲收到的票中的 vote\_myid 並廣播出去,另外將收到的票及本身更新後的票放入本身的票箱。

(7)統計選票

若是已經肯定有過半服務器承認了本身的投票(多是更新後的投票),則終止投票。不然繼續接收其它服務器的投票。

(8)更新服務器狀態

投票終止後,服務器開始更新自身狀態。若過半的票投給了本身,則將本身的服務器狀態更新爲 LEADING,不然將本身的狀態更新爲 FOLLOWING。

經過以上流程分析,咱們不難看出:要使 Leader 得到多數 Server 的支持,則 ZooKeeper 集羣節點數必須是奇數。且存活的節點數目不得少於 N + 1

每一個 Server 啓動後都會重複以上流程。在恢復模式下,若是是剛從崩潰狀態恢復的或者剛啓動的 server 還會從磁盤快照中恢復數據和會話信息,zk 會記錄事務日誌並按期進行快照,方便在恢復時進行狀態恢復。

4.2 原子廣播(Atomic Broadcast)

ZooKeeper 經過副本機制來實現高可用。

那麼,ZooKeeper 是如何實現副本機制的呢?答案是:ZAB 協議的原子廣播。

ZAB 協議的原子廣播要求:

全部的寫請求都會被轉發給 Leader,Leader 會以原子廣播的方式通知 Follow。當半數以上的 Follow 已經更新狀態持久化後,Leader 纔會提交這個更新,而後客戶端纔會收到一個更新成功的響應。這有些相似數據庫中的兩階段提交協議。

在整個消息的廣播過程當中,Leader 服務器會每一個事物請求生成對應的 Proposal,併爲其分配一個全局惟一的遞增的事務 ID(ZXID),以後再對其進行廣播。

5、ZooKeeper 應用

ZooKeeper 能夠用於發佈/訂閱、負載均衡、命令服務、分佈式協調/通知、集羣管理、Master 選舉、分佈式鎖和分佈式隊列等功能 。

5.1 命名服務

在分佈式系統中,一般須要一個全局惟一的名字,如生成全局惟一的訂單號等,ZooKeeper 能夠經過順序節點的特性來生成全局惟一 ID,從而能夠對分佈式系統提供命名服務。

5.2 配置管理

利用 ZooKeeper 的觀察機制,能夠將其做爲一個高可用的配置存儲器,容許分佈式應用的參與者檢索和更新配置文件。

5.3 分佈式鎖

能夠經過 ZooKeeper 的臨時節點和 Watcher 機制來實現分佈式鎖。

舉例來講,有一個分佈式系統,有三個節點 A、B、C,試圖經過 ZooKeeper 獲取分佈式鎖。

(1)訪問 /lock (這個目錄路徑由程序本身決定),建立 帶序列號的臨時節點(EPHEMERAL) 。

(2)每一個節點嘗試獲取鎖時,拿到 /locks節點下的全部子節點(id\_0000,id\_0001,id_0002),判斷本身建立的節點是否是最小的。

  • 若是是,則拿到鎖。

    釋放鎖:執行完操做後,把建立的節點給刪掉。

  • 若是不是,則監聽比本身要小 1 的節點變化。

(3)釋放鎖,即刪除本身建立的節點。

圖中,NodeA 刪除本身建立的節點 id_0000,NodeB 監聽到變化,發現本身的節點已是最小節點,便可獲取到鎖。

5.4 集羣管理

ZooKeeper 還能解決大多數分佈式系統中的問題:

  • 如能夠經過建立臨時節點來創建心跳檢測機制。若是分佈式系統的某個服務節點宕機了,則其持有的會話會超時,此時該臨時節點會被刪除,相應的監聽事件就會被觸發。
  • 分佈式系統的每一個服務節點還能夠將本身的節點狀態寫入臨時節點,從而完成狀態報告或節點工做進度彙報。
  • 經過數據的訂閱和發佈功能,ZooKeeper 還能對分佈式系統進行模塊的解耦和任務的調度。
  • 經過監聽機制,還能對分佈式系統的服務節點進行動態上下線,從而實現服務的動態擴容。

5.5 選舉 Leader 節點

分佈式系統一個重要的模式就是主從模式 (Master/Salves),ZooKeeper 能夠用於該模式下的 Matser 選舉。可讓全部服務節點去競爭性地建立同一個 ZNode,因爲 ZooKeeper 不能有路徑相同的 ZNode,必然只有一個服務節點可以建立成功,這樣該服務節點就能夠成爲 Master 節點。

5.6 隊列管理

ZooKeeper 能夠處理兩種類型的隊列:

  • 當一個隊列的成員都聚齊時,這個隊列纔可用,不然一直等待全部成員到達,這種是同步隊列。
  • 隊列按照 FIFO 方式進行入隊和出隊操做,例如實現生產者和消費者模型。

同步隊列用 ZooKeeper 實現的實現思路以下:

建立一個父目錄 /synchronizing,每一個成員都監控標誌(Set Watch)位目錄 /synchronizing/start 是否存在,而後每一個成員都加入這個隊列,加入隊列的方式就是建立 /synchronizing/member\_i 的臨時目錄節點,而後每一個成員獲取 / synchronizing 目錄的全部目錄節點,也就是 member\_i。判斷 i 的值是否已是成員的個數,若是小於成員個數等待 /synchronizing/start 的出現,若是已經相等就建立 /synchronizing/start。

參考資料

官方

書籍

文章

做者:ZhangPeng
相關文章
相關標籤/搜索