zookeeper核心之ZAB協議就這麼簡單!

背景

我們都知道 Zookeeper 是基於 ZAB 協議實現的,在介紹 ZAB 協議之前,先回顧一下 Zookeeper 的起源與發展。

Zookeeper 究竟是在什麼樣的時代背景下被提出?爲了解決了哪些棘手的問題?

Zookeeper 最早起源於雅虎研究院的一個研究小組。當時,研究人員發現,在雅虎的很多大型系統基本都需要依賴一個類似的系統來進行分佈式協調,但是這些系統都存在分佈式單點問題,所以雅虎的開發人員就試圖開發出一個通用的無單點問題的分佈式協調框架,以便讓開發人員將精力集中在處理業務邏輯上。

於是,Zookeeper 就誕生了!

Zookeeper 的出現不僅解決了分佈式系統下數據一致性的問題,而且經歷過線上驗證,無論是從性能、易用性、穩定性上來說,都是工業級產品的標準。可以說在分佈式系統中具有不可替代的核心地位,Hadoop、HBase、Storm 和 Solr 等大型分佈式系統都已經將 Zookeeper 作爲其核心組件,用於分佈式協調。即便 Zookeeper 如此優秀,但是 Zookeeper 依然是免費且開源的,全世界成千上萬的開發者都關注着它,陪同着一起成長和發展。

作爲一個開發者無論是爲了應付面試、晉升還是個人技術成長的需要,都需要對 Zookeeper 有所瞭解,而學習 Zookeeper 的關鍵就是理解其核心部分:

ZAB 協議, 全稱(Zookeeper Atomic Broadcast)Zookeeper 原子消息廣播協議。

它與 Paxos 類似,也是一種數據一致性的算法。

Zookeeper應該具備的特性

在 ZAB 協議的開發設計人員在協議設計之初並沒有要求 ZAB 具有很好的擴展性,最初只是爲了雅虎公司內部哪些高吞吐量、低延遲、健壯、簡單的分佈式系統場景設計的。基於 ZAB 協議,Zookeeper 實現了一種主備模式的系統架構來保持集羣中各副本之間數據的一致性,簡單架構圖如下:

Zookeeper 用一個單一的主進程來接收並處理客戶端的所有事物請求,並採用 ZAB 的原子廣播協議將服務器數據狀態以事物 Proposal 的形式廣播到所有的副本進程中去。這樣的模式就保證了,在同一時刻只有一個主進程來廣播服務器的狀態更變,因此能夠很好地處理客戶端大量的併發請求,這在 ZAB 協議中叫:消息廣播。

除此之外,在分佈式環境中事物的執行順序也會存在一定的先後關係,比如:事務 C 的寫入需要依賴事務 B 的寫入,而事務 B 寫入需要依賴事務 A 寫入。這種前後依賴的順序也對 ZAB 協議提出了一個要求:ZAB 協議需要保證如果一個狀態的變更被處理了,那麼所有其依賴的狀態變更都已經被提前處理了。也就是需要順序執行。

另外除了能正常廣播消息、消息的順序執行,主進程也可能隨時會因爲斷電、機器宕機等異常情況無法提供服務,因此,ZAB 協議還需要做到在當前主進程出現上述異常情況的時候依然能夠正常工作,這在 ZAB 協議中叫:崩潰恢復。

所以整個 ZAB 協議需要具備的核心特性已經被描述出來了,處理事務的請求的方式可以簡單理解爲:

所有的事務請求必須由一個全局唯一的服務器來協調處理,這樣的服務器叫:Leader 服務器。其他的服務器被稱爲 Follower 服務器。Leader 服務器將客戶端事務請求轉化成一個事務 Prososal(提議),並將改 Proposal 分發給集羣中所有的 Follower 服務器。之後 Leader 服務器接收了正確的反饋後,那麼 Leader 就會再次向所有的 Follower 服務器分發 Commit 消息,要求將前一個 Proposal 提交。

這就簡單闡述了ZAB 協議中消息廣播模式的部分內容

ZAB協議的兩種模式

ZAB 協議的包括兩種模式:崩潰恢復消息廣播

既然有兩種模式,那 Zookeeper 集羣什麼時候進入奔潰恢復模式?什麼時候進入消息廣播模式呢?

在進入奔潰恢復模式時 Zookeeper 集羣會進行 Leader 選舉,一般有兩種情況會發生選舉:

  • 當服務器啓動時期會進行 Leader 選舉。

  • 當服務器運行期 Leader 服務器的出現網絡中斷、奔潰退出、重啓等異常情況,或者當集羣中半數的服務器與該 Leader 服務器無法通信時,進入崩潰恢復模式,開始 Leader 選舉。

選舉出 Leader 服務器後,會進入消息廣播模式,開始接收處理客戶端的請求,前文已經描述,這裏不再贅述。

相關名詞概念

在深入講解 ZAB 協議的兩個模式之前,先解釋 Zookeeper 的幾個相關概念,方便理解 ZAB 協議:

三種角色

在前面提到 Zookeeper 的集羣中的服務器有 Leader 和 Follower ,但實際在 ZAB 協議中 Zookeeper 有三種角色,分別是 Leader、Folower、Observer,它們的分工各有不同:

  • Leader :負責整個Zookeeper 集羣工作機制中的核心,主要工作有一下兩個:
    • 事務請求的唯一調度和處理者,保證集羣事務處理的順序性
    • 集羣內部各服務器的調度者
  • Follower :它是 Leader 的追隨者,其主要工作有三個:
    • 處理客戶端的非實物請求,轉發事務請求給 Leader 服務器
    • 參與事務請求 Proposal 的投票
    • 參與 Leader 選舉投票
  • Observer :是 zookeeper 自 3.3.0 開始引入的一個角色,它不參與事務請求 Proposal 的投票,也不參與 Leader 選舉投票,只提供非事務的服務(查詢),通常在不影響集羣事務處理能力的前提下提升集羣的非事務處理能力。

三種狀態

在知道了 Zookeeper 中有三種角色後,不經提問: Zookeeper 是如何知道自己目前是什麼角色的呢?

在 ZAB 協議中定義:通過自身的狀態來區分自己的角色的,在運行期間各個進程可能出現以下三種狀態之一:

  • LOOKING:處在這個狀態時,會進入 Leader 選舉狀態
  • FOLLOWER:Follower 服務器和 Leader 服務器保持同步時的狀態
  • LEADING:Leader 服務器作爲主進程領導者的狀態

在組成 ZAB 協議的所有進程啓動的時候,初始化狀態都是 LOOKING 狀態,此時進程組中不存在 Leader,選舉之後纔有,在進行選舉成功後,就進入消息廣播模式,此時 Zookeeper 集羣中的角色狀態就不再是 LOOKING 狀態。

ZXID

前文我們知道 zookeeper 消息有嚴格的因果關係,因此必須將每一個事務請求按照先後順序來進行排序與處理。那 Zookeeper 是如何保持請求處理的順序的呢?其中非常關鍵的點就是 ZXID。

那 ZXID 究竟是怎麼發揮作用的呢?

Leader 服務器在接收到事務請求後,會爲每個事務請求生成對應的 Proposal 來進行廣播,並且在廣播事務 Proposal 之前,Leader 服務器會首先爲這個事務 Proposal 分配一個全局單調遞增的唯一 ID ,我們稱之爲事務 ID(即 ZXID)。

ZXID 的設計也很有特點,是一個全局有序的 64 位的數字,可以分爲兩個部分:

  • 高 32 位是: epoch(紀元),代表着週期,每當選舉產生一個新的 Leader 服務器時就會取出其本地日誌中最大事務的 ZXID ,解析出 epoch(紀元)值操作加 1作爲新的 epoch ,並將低 32 位置零。
  • 低 32 位是: counter(計數器),它是一個簡單的單調遞增的計數器,針對客戶端的每個事務請求都會進行加 1 操作;

這裏低 32 位 counter(計數器)單調遞增還好理解,高 32 位 epoch(紀元)每次選舉加 1 也許有些同學就有疑問了,爲什麼 epoch(紀元)每次選需要舉加 1 ,它在整個 ZAB 協議中有什麼作用?

我們知道每當選舉產生一個新的 Leader 服務器時生成一個新的 epoch(紀元)值,而在前文我們知道,服務運行過程中觸發選舉 Leader 的條件是:Leader 服務器的出現網絡中斷、奔潰退出、重啓等異常情況,或者當集羣中半數的服務器與該 Leader 服務器無法通信時

這說明整個 Zookeeper 集羣此時處於一個異常的情況下,而在發生異常前,消息廣播進行到哪一步驟我們根本不知道,集羣中的其他 Follower 節點從這種崩潰恢復狀態重新選舉出 Leader 後,如果老 Leader 又恢復了連接進入集羣。此時老 Leader 的 epoch 肯定會小於新 Leader 的 epoch,這時就將老 Leader 變成 Follower,對新的 Leader 進行數據同步。即便這時老 Leader 對其他的 Follower 節點發送了請求,Follower 節點也會比較 ZXID 的值,因爲高 32 位加 1 了, Follower 的 epoch(紀元)大於老 Leader 的 epoch(紀元),所以 Follower 會忽略這個請求。

這像改朝換代一樣,前朝的劍不能斬本朝的官。

消息廣播模式

知道了這些名詞,和上文提到的零散的知識點,其實崩潰恢復模式和消息廣播模式的過程大家大致有所瞭解了。

先看看消息廣播模式吧!

消息廣播的模式的過程簡圖如下所示:

整個過程類似一個二階段提交的過程,但卻有所不同,ZAB 協議簡化了二階段提交模型,在超過半數的 Follower 服務器已經反饋 ACK 之後就開始提交事務 Prososal 了,無需等待所有服務器響應。

結合上圖,看看消息廣播的具體細節:

  • Leader 服務器接收到請求後在進行廣播事務 Proposal 之前會爲這個事務分配一個 ZXID,再進行廣播。
  • Leader 服務器會爲每個 Follower 服務器都各自分配一個單獨的隊列,然後將需要廣播的事務 Proposal 依次放入這些隊列中去,並根據 FIFO 策略進行消息的發送。
  • 每個Follower 服務器在接收到後都會將其以事務日誌的形式寫入到本地磁盤中,並且在成功寫入後返回 Leader 服務器一個 ACK 響應。
  • 當有超過半數的服務器 ACK 響應後,Leader 就會廣播一個 Commit 消息給所有的 Follower 服務器,Follower 接收到後就完成對事務的提交操作。

在畫一張詳細點的流程圖,更直觀:

這就完成了整個消息廣播了!

崩潰恢復模式

前文已經反覆提過崩潰恢復模式了,其實就是重新選舉出新的 Leader 服務器,選舉完成後 Follower 服務器在再去同步 Leader 的數據。

運行中的服務再次進行重新選舉,一定是出現某種異常,我們知道在出現異常情況之前 Leader 的消息廣播可能會處在任何一個階段,有可能客戶端的請求只是在 Leader 服務器上提出並未被提交,也可能請求已經被 Leader 服務器提交。

ZAB 協議對於不同階段的出現的數據不一致的情況做了兼容,保證:

  • 已經在 Leader 服務器上提交的事務,最終會被所有服務器都提交
  • 只在 Leader 服務器上提出的事務,要丟棄

針對以上的兩個要求,在進行 Leader 選舉時,之需要選舉出集羣中 ZXID 最大的事務 Proposal 即可,這樣就可以省去 Leader 服務器檢查 Proposal 的提交和丟棄工作了。因爲 Leader 服務器的事務是最大的,一切以 Leader 服務器的數據爲標準即可。

ZXID 在集羣中其實並不是唯一的,所以也有可能出現多 Follower 服務器 ZXID 相同的情況,這時候就需要比較 Zookeeper 的 SID 值。什麼是 SID?SID 是一個數字,和 zookeeper 的 myid 一致,myid 就不要解釋了,安裝過 Zookeeper 的都知道,每臺服務器都需要配置一個這樣的文件,裏面只有一個數字,用來標識這臺服務器。因爲每臺機器的 myid 配置都不一樣,所以集羣選舉的時不會出現相等的情況。

選舉時,比較大小的源碼如下:

前面已經說過,出現選舉 Leader 可能會出現兩種情況:

  • 服務啓動時期,發起選舉
  • 服務運行期間,出現異常,發起選舉

但無論是啓動期還是運行期進行 Leader 選舉,其選舉過程都差不太多,我簡單畫個流程圖:

<img src="https://upload-images.jianshu.io/upload_images/2710833-d87f4a39511a8630.png" alt="image.png" style="zoom:50%;" />

結合上圖,奔潰恢復模式下 Leader 選舉的過程細節如下:

  • 檢測節點處於 LOOKING 階段,開發選舉 Leader
  • 發起投票時有兩種情況:
    • 在服務啓動的初始階段,每個服務器都會投票給自己以(myid,zxid)的信息形式發送,那初始階段沒有 zxid 值,就會發送(myid,0)
    • 在服務器運行期間,每個服務器上的 zxid 都有值,且 zxid 都不相同,所以就正常發送(myid,zxid)
  • 各節點收到信息後將收到的(myid,zxid)和自己的比較,比較的過程前面已經說過,這裏不再贅述
  • 然後判斷是否有半數的機器投票選出 Leader,如果否,在進入新一輪投票,直到選出
  • 選出 Leader 後,其他節點就變成 Follower 角色,並向 Leader 發送自己服務器的最大 zxid ,Leader 服務器收到後會和自己本地的提議緩存隊列進行比較,判斷使用那種策略進行同步(後面詳細說明同步的四種策略)
  • 當同步完成,集羣就可以正常的處理請求了,就進入消息廣播模式了。

這就是崩潰恢復模式下選舉 Leader 的過程了!

下面再簡單介紹下數據同步的四種策略,這四種同步策略保證了Zookeeper 集羣中的數據一致性,也解決了前文提出的兩個問題,兼容了各種數據不一致的場景。

數據同步的四種策略

在數據同步之前,Leader 服務器會進行數據同步的初始化,首先會從 Zookeeper 的內存數據庫中提取出事務前期對應的提議緩存隊列,同時會初始化三個 ZXID 的值:

  • peerLastZxid:這是 Follower 的最後處理 ZXID
  • minCommittedLog:Leader 服務器的提議緩存隊列中 最小的 ZXID
  • maxCommittedLog:Leader 服務器的提議緩存隊列中 最大的 ZXID

根據這三個參數,就可以確定四種同步方式,分別爲:

  • 直接差異化同步
    • 場景:當 minCommittedLog < peerLastZxid < maxCommittedLog 時
  • 先回滾在差異化同步
    • 場景:假如集羣有 A、B、C 三臺機器,此時 A 是 Leader 但是 A 掛了,在掛之前 A 生成了一個提議假設是:03,然後集羣有重新選舉 B 爲新的 Leader,此時生成的的提議緩存隊列爲:01~02,B 和 C 進行同步之後,生成新的紀元,ZXID 從 10 開始計數,集羣進入廣播模式處理了部分請求,假設現在 ZXID 執行到 15 這個值,此時 A 恢復了加入集羣,這時候就比較 A 最後提交的 ZXID:peerLastZxid 與 minCommittedLog、maxCommittedLog 的關係。此時雖然符合直接差異化同步:minCommittedLog < peerLastZxid < maxCommittedLog 這樣的關係,但是提議緩存隊列中卻沒有這個 ZXID ,這時候就需要先回滾,在進行同步。
  • 僅回滾同步
    • 場景:這裏和先回滾在差異化同步類似,直接回滾就可以。
  • 全量同步
    • 場景:peerLastZxid < minCommittedLog,當遠遠落後 Leader 的數據時,直接全量同步。

這就是四種同步策略,這幾種同步方式也解決了上文提出的問題:

  • 只在 Leader 服務器上提出的事務,要丟棄(這個問題會在同步時,會進行回滾,使得只在 Leader 服務器上提出的事務丟棄)

這些就是整個 ZAB 協議中崩潰恢復的內容。

ZAB協議和Paxos算法的區別

ZAB協議看起來和Paxos有着相同之處,但它並不是Paxos的典型實現,其實還是有一些區別,ZAB協議中額外添加了一個同步的階段,兩者設計目標也不太一樣,ZAB協議主要用於構建一個高可用的分佈式數據主備系統,而Paxos算法則是用於構建一個分佈式一致性的狀態機。

總結

Zookeeper 作爲出色的分佈式協調服務,目前讀 QPS 達到 12w,出色的性能也讓開發者更加青睞,其 ZAB 協議的核心分爲兩個部分:崩潰恢復、消息廣播。

典型的應用場景有:

  • 數據發佈/訂閱、負載均衡
  • 命名服務
  • 分佈式協調通知
  • 集羣管理
  • Master選舉
  • 分佈式鎖
  • 分佈式隊列
  • 用 Zookeeper 避免腦裂

除此之外在大數據領域也有應用,例如:

  • Hadoop
  • HBase
  • Kafka

在阿里巴巴集團內部實踐的 Zookeeper 的產品也有很多,如:

  • 消息中間件:Metamorphosis
  • RPC 服務框架:Dubbo
  • 基於 MySQL Binlog 的增量訂閱和消費組件:Cancel
  • 分佈式數據庫同步系統:Otter
  • 實時計算引擎:JStorm

歡迎關注我的個人微信公衆號:java之旅

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章