Zookeeper架構及FastLeaderElection機制

原文連接:http://www.jasongj.com/zookeeper/fastleaderelection/算法

Zookeeper是什麼

Zookeeper是一個分佈式協調服務,可用於服務發現,分佈式鎖,分佈式領導選舉,配置管理等。服務器

這一切的基礎,都是Zookeeper提供了一個相似於Linux文件系統的樹形結構(可認爲是輕量級的內存文件系統,但只適合存少許信息,徹底不適合存儲大量文件或者大文件),同時提供了對於每一個節點的監控與通知機制。網絡

既然是一個文件系統,就不得不提Zookeeper是如何保證數據的一致性的。本文將介紹Zookeeper如何保證數據一致性,如何進行領導選舉,以及數據監控/通知機制的語義保證。數據結構

Zookeeper架構

角色

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

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

Zookeeper Architecture

原子廣播(ZAB)

爲了保證寫操做的一致性與可用性,Zookeeper專門設計了一種名爲原子廣播(ZAB)的支持崩潰恢復的一致性協議。基於該協議,Zookeeper實現了一種主從模式的系統架構來保持集羣中各個副本之間的數據一致性。併發

根據ZAB協議,全部的寫操做都必須經過Leader完成,Leader寫入本地日誌後再複製到全部的Follower節點。分佈式

一旦Leader節點沒法工做,ZAB協議可以自動從Follower節點中從新選出一個合適的替代者,即新的Leader,該過程即爲領導選舉。該領導選舉過程,是ZAB協議中最爲重要和複雜的過程。性能

寫操做

寫Leader

經過Leader進行寫操做流程以下圖所示spa


Zookeeper Leader Write

由上圖可見,經過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的數據從而在處理讀請求時能夠返回儘量新的數據

寫Follower/Observer

經過Follower/Observer進行寫操做流程以下圖所示:


Zookeeper Follower/Observer Write

從上圖可見

  • Follower/Observer都可接受寫請求,但不能直接處理,而須要將寫請求轉發給Leader處理
  • 除了多了一步請求轉發,其它流程與直接寫Leader無任何區別

讀操做

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


Zookeeper Read

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

FastLeaderElection原理

術語介紹

myid
每一個Zookeeper服務器,都須要在數據文件夾下建立一個名爲myid的文件,該文件包含整個Zookeeper集羣惟一的ID(整數)。例如某Zookeeper集羣包含三臺服務器,hostname分別爲zoo一、zoo2和zoo3,其myid分別爲一、2和3,則在配置文件中其ID與hostname必須一一對應,以下所示。在該配置文件中,server.後面的數據即爲myid

1
2
3
server.1=zoo1:2888:3888
server.2=zoo2:2888:3888
server.3=zoo3:2888:3888

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

支持的領導選舉算法

可經過electionAlg配置項設置Zookeeper用於領導選舉的算法。

到3.4.10版本爲止,可選項有

  • 0 基於UDP的LeaderElection
  • 1 基於UDP的FastLeaderElection
  • 2 基於UDP和認證的FastLeaderElection
  • 3 基於TCP的FastLeaderElection

在3.4.10版本中,默認值爲3,也即基於TCP的FastLeaderElection。另外三種算法已經被棄用,而且有計劃在以後的版本中將它們完全刪除而再也不支持。

FastLeaderElection

FastLeaderElection選舉算法是標準的Fast Paxos算法實現,可解決LeaderElection選舉算法收斂速度慢的問題。

服務器狀態

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

選票數據結構

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

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

投票流程

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

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

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

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

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

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

選票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並廣播出去,另外將收到的票及本身更新後的票放入本身的票箱

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

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

幾種領導選舉場景

集羣啓動領導選舉

初始投票給本身
集羣剛啓動時,全部服務器的logicClock都爲1,zxid都爲0。

各服務器初始化後,都投票給本身,並將本身的一票存入本身的票箱,以下圖所示。


Cluster start election step 1


在上圖中,(1, 1, 0)第一位數表明投出該選票的服務器的logicClock,第二位數表明被推薦的服務器的myid,第三位表明被推薦的服務器的最大的zxid。因爲該步驟中全部選票都投給本身,因此第二位的myid便是本身的myid,第三位的zxid便是本身的zxid。

此時各自的票箱中只有本身投給本身的一票。

更新選票
服務器收到外部投票後,進行選票PK,相應更新本身的選票並廣播出去,並將合適的選票存入本身的票箱,以下圖所示。


Cluster start election step 2


服務器1收到服務器2的選票(1, 2, 0)和服務器3的選票(1, 3, 0)後,因爲全部的logicClock都相等,全部的zxid都相等,所以根據myid判斷應該將本身的選票按照服務器3的選票更新爲(1, 3, 0),並將本身的票箱所有清空,再將服務器3的選票與本身的選票存入本身的票箱,接着將本身更新後的選票廣播出去。此時服務器1票箱內的選票爲(1, 3),(3, 3)。

同理,服務器2收到服務器3的選票後也將本身的選票更新爲(1, 3, 0)並存入票箱而後廣播。此時服務器2票箱內的選票爲(2, 3),(3, ,3)。

服務器3根據上述規則,無須更新選票,自身的票箱內選票仍爲(3, 3)。

服務器1與服務器2更新後的選票廣播出去後,因爲三個服務器最新選票都相同,最後三者的票箱內都包含三張投給服務器3的選票。

根據選票肯定角色
根據上述選票,三個服務器一致認爲此時服務器3應該是Leader。所以服務器1和2都進入FOLLOWING狀態,而服務器3進入LEADING狀態。以後Leader發起並維護與Follower間的心跳。


Cluster start election step 3

Follower重啓

Follower重啓投票給本身
Follower重啓,或者發生網絡分區後找不到Leader,會進入LOOKING狀態併發起新的一輪投票。


Follower restart election step 1

發現已有Leader後成爲Follower
服務器3收到服務器1的投票後,將本身的狀態LEADING以及選票返回給服務器1。服務器2收到服務器1的投票後,將本身的狀態FOLLOWING及選票返回給服務器1。此時服務器1知道服務器3是Leader,而且經過服務器2與服務器3的選票能夠肯定服務器3確實獲得了超過半數的選票。所以服務器1進入FOLLOWING狀態。


Follower restart election step 2

Leader重啓

Follower發起新投票
Leader(服務器3)宕機後,Follower(服務器1和2)發現Leader不工做了,所以進入LOOKING狀態併發起新的一輪投票,而且都將票投給本身。


Leader restart election step 1

廣播更新選票
服務器1和2根據外部投票肯定是否要更新自身的選票。這裏有兩種狀況

  • 服務器1和2的zxid相同。例如在服務器3宕機前服務器1與2徹底與之同步。此時選票的更新主要取決於myid的大小
  • 服務器1和2的zxid不一樣。在舊Leader宕機以前,其所主導的寫操做,只需過半服務器確認便可,而不需全部服務器確認。換句話說,服務器1和2可能一個與舊Leader同步(即zxid與之相同)另外一個不一樣步(即zxid比之小)。此時選票的更新主要取決於誰的zxid較大

在上圖中,服務器1的zxid爲11,而服務器2的zxid爲10,所以服務器2將自身選票更新爲(3, 1, 11),以下圖所示。


Leader restart election step 2

選出新Leader
通過上一步選票更新後,服務器1與服務器2均將選票投給服務器1,所以服務器2成爲Follower,而服務器1成爲新的Leader並維護與服務器2的心跳。


Leader restart election step 3

舊Leader恢復後發起選舉
舊的Leader恢復後,進入LOOKING狀態併發起新一輪領導選舉,並將選票投給本身。此時服務器1會將本身的LEADING狀態及選票(3, 1, 11)返回給服務器3,而服務器2將本身的FOLLOWING狀態及選票(3, 1, 11)返回給服務器3。以下圖所示。


Leader restart election step 4

舊Leader成爲Follower
服務器3瞭解到Leader爲服務器1,且根據選票瞭解到服務器1確實獲得過半服務器的選票,所以本身進入FOLLOWING狀態。


Leader restart election step 5

一致性保證

ZAB協議保證了在Leader選舉的過程當中,已經被Commit的數據不會丟失,未被Commit的數據對客戶端不可見。

Commit過的數據不丟失

Failover前狀態
爲更好演示Leader Failover過程,本例中共使用5個Zookeeper服務器。A做爲Leader,共收到P一、P二、P3三條消息,而且Commit了1和2,且整體順序爲P一、P二、C一、P三、C2。根據順序性原則,其它Follower收到的消息的順序確定與之相同。其中B與A徹底同步,C收到P一、P二、C1,D收到P一、P2,E收到P1,以下圖所示。


Leader Failover step 1

這裏要注意

  • 因爲A沒有C3,意味着收到P3的服務器的總個數不會超過一半,也即包含A在內最多隻有兩臺服務器收到P3。在這裏A和B收到P3,其它服務器均未收到P3
  • 因爲A已寫入C一、C2,說明它已經Commit了P一、P2,所以整個集羣有超過一半的服務器,即最少三個服務器收到P一、P2。在這裏全部服務器都收到了P1,除E外其它服務器也都收到了P2

選出新Leader
舊Leader也即A宕機後,其它服務器根據上述FastLeaderElection算法選出B做爲新的Leader。C、D和E成爲Follower且以B爲Leader後,會主動將本身最大的zxid發送給B,B會將Follower的zxid與自身zxid間的全部被Commit過的消息同步給Follower,以下圖所示。


Leader Failover step 2

在上圖中

  • P1和P2都被A Commit,所以B會經過同步保證P一、P二、C1與C2都存在於C、D和E中
  • P3因爲未被A Commit,同時倖存的全部服務器中P3未存在於大多數據服務器中,所以它不會被同步到其它Follower

通知Follower可對外服務
同步完數據後,B會向D、C和E發送NEWLEADER命令並等待大多數服務器的ACK(下圖中D和E已返回ACK,加上B自身,已經佔集羣的大多數),而後向全部服務器廣播UPTODATE命令。收到該命令後的服務器便可對外提供服務。


Leader Failover step 3

未Commit過的消息對客戶端不可見

在上例中,P3未被A Commit過,同時由於沒有過半的服務器收到P3,所以B也未Commit P3(若是有過半服務器收到P3,即便A未Commit P3,B會主動Commit P3,即C3),因此它不會將P3廣播出去。

具體作法是,B在成爲Leader後,先判斷自身未Commit的消息(本例中即P3)是否存在於大多數服務器中從而決定是否要將其Commit。而後B可得出自身所包含的被Commit過的消息中的最小zxid(記爲min_zxid)與最大zxid(記爲max_zxid)。C、D和E向B發送自身Commit過的最大消息zxid(記爲max_zxid)以及未被Commit過的全部消息(記爲zxid_set)。B根據這些信息做出以下操做

  • 若是Follower的max_zxid與Leader的max_zxid相等,說明該Follower與Leader徹底同步,無須同步任何數據
  • 若是Follower的max_zxid在Leader的(min_zxid,max_zxid)範圍內,Leader會經過TRUNC命令通知Follower將其zxid_set中大於Follower的max_zxid(若是有)的全部消息所有刪除

上述操做保證了未被Commit過的消息不會被Commit從而對外不可見。

上述例子中Follower上並不存在未被Commit的消息。但可考慮這種狀況,若是將上述例子中的服務器數量從五增長到七,服務器F包含P一、P二、C一、P3,服務器G包含P一、P2。此時服務器F、A和B都包含P3,可是由於票數未過半,所以B做爲Leader不會Commit P3,而會經過TRUNC命令通知F刪除P3。以下圖所示。


Leader Failover step 4

總結

    • 因爲使用主從複製模式,全部的寫操做都要由Leader主導完成,而讀操做可經過任意節點完成,所以Zookeeper讀性能遠好於寫性能,更適合讀多寫少的場景
    • 雖然使用主從複製模式,同一時間只有一個Leader,可是Failover機制保證了集羣不存在單點失敗(SPOF)的問題
    • ZAB協議保證了Failover過程當中的數據一致性
    • 服務器收到數據後先寫本地文件再進行處理,保證了數據的持久性
相關文章
相關標籤/搜索