原文連接:http://www.jasongj.com/zookeeper/fastleaderelection/算法
Zookeeper是一個分佈式協調服務,可用於服務發現,分佈式鎖,分佈式領導選舉,配置管理等。服務器
這一切的基礎,都是Zookeeper提供了一個相似於Linux文件系統的樹形結構(可認爲是輕量級的內存文件系統,但只適合存少許信息,徹底不適合存儲大量文件或者大文件),同時提供了對於每一個節點的監控與通知機制。網絡
既然是一個文件系統,就不得不提Zookeeper是如何保證數據的一致性的。本文將介紹Zookeeper如何保證數據一致性,如何進行領導選舉,以及數據監控/通知機制的語義保證。數據結構
Zookeeper集羣是一個基於主從複製的高可用集羣,每一個服務器承擔以下三種角色中的一種架構
爲了保證寫操做的一致性與可用性,Zookeeper專門設計了一種名爲原子廣播(ZAB)的支持崩潰恢復的一致性協議。基於該協議,Zookeeper實現了一種主從模式的系統架構來保持集羣中各個副本之間的數據一致性。併發
根據ZAB協議,全部的寫操做都必須經過Leader完成,Leader寫入本地日誌後再複製到全部的Follower節點。分佈式
一旦Leader節點沒法工做,ZAB協議可以自動從Follower節點中從新選出一個合適的替代者,即新的Leader,該過程即爲領導選舉。該領導選舉過程,是ZAB協議中最爲重要和複雜的過程。性能
經過Leader進行寫操做流程以下圖所示spa
由上圖可見,經過Leader進行寫操做,主要分爲五步:設計
這裏要注意
經過Follower/Observer進行寫操做流程以下圖所示:
從上圖可見
Leader/Follower/Observer均可直接處理讀請求,從本地內存中讀取數據並返回給客戶端便可。
因爲處理讀請求不須要服務器之間的交互,Follower/Observer越多,總體可處理的讀請求量越大,也即讀性能越好。
myid
每一個Zookeeper服務器,都須要在數據文件夾下建立一個名爲myid的文件,該文件包含整個Zookeeper集羣惟一的ID(整數)。例如某Zookeeper集羣包含三臺服務器,hostname分別爲zoo一、zoo2和zoo3,其myid分別爲一、2和3,則在配置文件中其ID與hostname必須一一對應,以下所示。在該配置文件中,server.
後面的數據即爲myid
1 |
server.1=zoo1: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的LeaderElection1
基於UDP的FastLeaderElection2
基於UDP和認證的FastLeaderElection3
基於TCP的FastLeaderElection在3.4.10版本中,默認值爲3,也即基於TCP的FastLeaderElection。另外三種算法已經被棄用,而且有計劃在以後的版本中將它們完全刪除而再也不支持。
FastLeaderElection選舉算法是標準的Fast Paxos算法實現,可解決LeaderElection選舉算法收斂速度慢的問題。
每一個服務器在進行領導選舉時,會發送以下關鍵信息
自增選舉輪次
Zookeeper規定全部有效的投票都必須在同一輪次中。每一個服務器在開始新一輪投票時,會先對本身維護的logicClock進行自增操做。
初始化選票
每一個服務器在廣播本身的選票前,會將本身的投票箱清空。該投票箱記錄了所收到的選票。例:服務器2投票給服務器3,服務器3投票給服務器1,則服務器1的投票箱爲(2, 3), (3, 1), (1, 1)。票箱中只會記錄每一投票者的最後一票,如投票者更新本身的選票,則其它服務器收到該新選票後會在本身票箱中更新該服務器的選票。
發送初始化選票
每一個服務器最開始都是經過廣播把票投給本身。
接收外部投票
服務器會嘗試從其它服務器獲取投票,並記入本身的投票箱內。若是沒法獲取任何外部投票,則會確認本身是否與集羣中其它服務器保持着有效鏈接。若是是,則再次發送本身的投票;若是否,則立刻與之創建鏈接。
判斷選舉輪次
收到外部投票後,首先會根據投票信息中所包含的logicClock來進行不一樣處理
選票PK
選票PK是基於(self_id, self_zxid)與(vote_id, vote_zxid)的對比
統計選票
若是已經肯定有過半服務器承認了本身的投票(多是更新後的投票),則終止投票。不然繼續接收其它服務器的投票。
更新服務器狀態
投票終止後,服務器開始更新自身狀態。若過半的票投給了本身,則將本身的服務器狀態更新爲LEADING,不然將本身的狀態更新爲FOLLOWING
初始投票給本身
集羣剛啓動時,全部服務器的logicClock都爲1,zxid都爲0。
各服務器初始化後,都投票給本身,並將本身的一票存入本身的票箱,以下圖所示。
在上圖中,(1, 1, 0)第一位數表明投出該選票的服務器的logicClock,第二位數表明被推薦的服務器的myid,第三位表明被推薦的服務器的最大的zxid。因爲該步驟中全部選票都投給本身,因此第二位的myid便是本身的myid,第三位的zxid便是本身的zxid。
此時各自的票箱中只有本身投給本身的一票。
更新選票
服務器收到外部投票後,進行選票PK,相應更新本身的選票並廣播出去,並將合適的選票存入本身的票箱,以下圖所示。
服務器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間的心跳。
Follower重啓投票給本身
Follower重啓,或者發生網絡分區後找不到Leader,會進入LOOKING狀態併發起新的一輪投票。
發現已有Leader後成爲Follower
服務器3收到服務器1的投票後,將本身的狀態LEADING以及選票返回給服務器1。服務器2收到服務器1的投票後,將本身的狀態FOLLOWING及選票返回給服務器1。此時服務器1知道服務器3是Leader,而且經過服務器2與服務器3的選票能夠肯定服務器3確實獲得了超過半數的選票。所以服務器1進入FOLLOWING狀態。
Follower發起新投票
Leader(服務器3)宕機後,Follower(服務器1和2)發現Leader不工做了,所以進入LOOKING狀態併發起新的一輪投票,而且都將票投給本身。
廣播更新選票
服務器1和2根據外部投票肯定是否要更新自身的選票。這裏有兩種狀況
在上圖中,服務器1的zxid爲11,而服務器2的zxid爲10,所以服務器2將自身選票更新爲(3, 1, 11),以下圖所示。
選出新Leader
通過上一步選票更新後,服務器1與服務器2均將選票投給服務器1,所以服務器2成爲Follower,而服務器1成爲新的Leader並維護與服務器2的心跳。
舊Leader恢復後發起選舉
舊的Leader恢復後,進入LOOKING狀態併發起新一輪領導選舉,並將選票投給本身。此時服務器1會將本身的LEADING狀態及選票(3, 1, 11)返回給服務器3,而服務器2將本身的FOLLOWING狀態及選票(3, 1, 11)返回給服務器3。以下圖所示。
舊Leader成爲Follower
服務器3瞭解到Leader爲服務器1,且根據選票瞭解到服務器1確實獲得過半服務器的選票,所以本身進入FOLLOWING狀態。
ZAB協議保證了在Leader選舉的過程當中,已經被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
舊Leader也即A宕機後,其它服務器根據上述FastLeaderElection算法選出B做爲新的Leader。C、D和E成爲Follower且以B爲Leader後,會主動將本身最大的zxid發送給B,B會將Follower的zxid與自身zxid間的全部被Commit過的消息同步給Follower,以下圖所示。
在上圖中
通知Follower可對外服務
同步完數據後,B會向D、C和E發送NEWLEADER命令並等待大多數服務器的ACK(下圖中D和E已返回ACK,加上B自身,已經佔集羣的大多數),而後向全部服務器廣播UPTODATE命令。收到該命令後的服務器便可對外提供服務。
在上例中,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根據這些信息做出以下操做
上述操做保證了未被Commit過的消息不會被Commit從而對外不可見。
上述例子中Follower上並不存在未被Commit的消息。但可考慮這種狀況,若是將上述例子中的服務器數量從五增長到七,服務器F包含P一、P二、C一、P3,服務器G包含P一、P2。此時服務器F、A和B都包含P3,可是由於票數未過半,所以B做爲Leader不會Commit P3,而會經過TRUNC命令通知F刪除P3。以下圖所示。