本文首發於http://www.yidooo.net/2014/10/18/zookeeper-leader-election.html 轉載請註明出處
當Leader崩潰或者Leader失去大多數的Follower,這時候zk進入恢復模式,恢復模式需要重新選舉出一個新的Leader,讓所有的Server都恢復到一個正確的狀態。Zookeeper中Leader的選舉採用了三種演算法:
- LeaderElection
- FastLeaderElection
- AuthFastLeaderElection
並且在配置檔案中是可配置的,對應的配置項為electionAlg。
背景知識
Zookeeper Server的狀態可分為四種:
- LOOKING:尋找Leader
- LEADING:Leader狀態,對應的節點為Leader。
- FOLLOWING:Follower狀態,對應的節點為Follower。
- OBSERVING:Observer狀態,對應節點為Observer,該節點不參與Leader選舉。
成為Leader的必要條件: Leader要具有最高的zxid;當叢集的規模是n時,叢集中大多數的機器(至少n/2+1)得到響應並follow選出的Leader。
心跳機制:Leader與Follower利用PING來感知對方的是否存活,當Leader無法相應PING時,將重新發起Leader選舉。
術語
zxid:zookeeper transaction id, 每個改變Zookeeper狀態的操作都會形成一個對應的zxid,並記錄到transaction log中。 這個值越大,表示更新越新。
electionEpoch/logicalclock:邏輯時鐘,用來判斷是否為同一次選舉。每呼叫一次選舉函式,logicalclock自增1,並且在選舉過程中如果遇到election比當前logicalclock大的值,就更新本地logicalclock的值。
peerEpoch: 表示節點的Epoch。
LeaderElection選舉演算法
LeaderElection是Fast Paxos最簡單的一種實現,每個Server啟動以後都詢問其它的Server它要投票給誰,收到所有Server回覆以後,就計算出zxid最大的哪個Server,並將這個Server相關資訊設定成下一次要投票的Server。該演算法於Zookeeper 3.4以後的版本廢棄。
選舉演算法流程如下:
- 選舉執行緒首先向所有Server發起一次詢問(包括自己);
- 選舉執行緒收到回覆後,驗證是否是自己發起的詢問(驗證xid是否一致),然後獲取對方的id(myid),並儲存到當前詢問物件列表中,最後獲取對方提議的leader相關資訊(id,zxid),並將這些資訊儲存到當次選舉的投票記錄表中;
- 收到所有Server回覆以後,就計算出zxid最大的那個Server,並將這個Server相關資訊設定成下一次要投票的Server;
- 執行緒將當前zxid最大的Server設定為當前Server要推薦的Leader,如果此時獲勝的Server獲得多數Server票數, 設定當前推薦的leader為獲勝的Server,將根據獲勝的Server相關資訊設定自己的狀態,否則,繼續這個過程,直到leader被選舉出來。
通過流程分析我們可以得出:要使Leader獲得多數Server的支援,則Server總數必須是奇數2n+1,且存活的Server的數目不得少於n+1.
異常問題的處理:
- 選舉過程中,Server的加入
當一個Server啟動時它都會發起一次選舉,此時由選舉執行緒發起相關流程,那麼每個 Serve r都會獲得當前zxi d最大的哪個Serve r是誰,如果當次最大的Serve r沒有獲得n/2+1 個票數,那麼下一次投票時,他將向zxid最大的Server投票,重複以上流程,最後一定能選舉出一個Leader。 - 選舉過程中,Server的退出
只要保證n/2+1個Server存活就沒有任何問題,如果少於n/2+1個Server 存活就沒辦法選出Leader。 - 選舉過程中,Leader死亡
當選舉出Leader以後,此時每個Server應該是什麼狀態(FLLOWING)都已經確定,此時由於Leader已經死亡我們就不管它,其它的Fllower按正常的流程繼續下去,當完成這個流程以後,所有的Fllower都會向Leader傳送Ping訊息,如果無法ping通,就改變自己的狀為(FLLOWING ==> LOOKING),發起新的一輪選舉。 - 選舉完成以後,Leader死亡
處理過程同上。 - 雙主問題
Leader的選舉是保證只產生一個公認的Leader的,而且Follower重新選舉與舊Leader恢復並退出基本上是同時發生的,當Follower無法ping同Leader是就認為Leader已經出問題開始重新選舉,Leader收到Follower的ping沒有達到半數以上則要退出Leader重新選舉。
FastLeaderElection選舉演算法
由於LeaderElection收斂速度較慢,所以Zookeeper引入了FastLeaderElection選舉演算法,FastLeaderElection也成了Zookeeper預設的Leader選舉演算法。
FastLeaderElection是標準的Fast Paxos的實現,它首先向所有Server提議自己要成為leader,當其它Server收到提議以後,解決 epoch 和 zxid 的衝突,並接受對方的提議,然後向對方傳送接受提議完成的訊息。FastLeaderElection演算法通過非同步的通訊方式來收集其它節點的選票,同時在分析選票時又根據投票者的當前狀態來作不同的處理,以加快Leader的選舉程式。
演算法流程
資料恢復階段
每個ZooKeeper Server讀取當前磁碟的資料(transaction log),獲取最大的zxid。
傳送選票
每個參與投票的ZooKeeper Server向其他Server傳送自己所推薦的Leader,這個協議中包括幾部分資料:
- 所推舉的Leader id。在初始階段,第一次投票所有Server都推舉自己為Leader。
- 本機的最大zxid值。這個值越大,說明該Server的資料越新。
- logicalclock。這個值從0開始遞增,每次選舉對應一個值,即在同一次選舉中,這個值是一致的。這個值越大說明選舉程式越新。
- 本機的所處狀態。包括LOOKING,FOLLOWING,OBSERVING,LEADING。
處理選票
每臺Server將自己的資料傳送給其他Server之後,同樣也要接受其他Server的選票,並做一下處理。
如果Sender的狀態是LOOKING
- 如果傳送過來的logicalclock大於目前的logicalclock。說明這是更新的一次選舉,需要更新本機的logicalclock,同事清空已經收集到的選票,因為這些資料已經不再有效。然後判斷是否需要更新自己的選舉情況。首先判斷zxid,zxid大者勝出;如果相同比較leader id,大者勝出。
- 如果傳送過來的logicalclock小於於目前的logicalclock。說明對方處於一個比較早的選舉程式,只需要將本機的資料傳送過去即可。
- 如果傳送過來的logicalclock等於目前的logicalclock。根據收到的zxid和leader id更新選票,然後廣播出去。
當Server處理完選票後,可能需要對Server的狀態進行更新:
- 判斷伺服器是否已經收集到所有的伺服器的選舉狀態。如果是根據選舉結果設定自己的角色(FOLLOWING or LEADER),然後退出選舉。
- 如果沒有收到沒有所有伺服器的選舉狀態,也可以判斷一下根據以上過程之後更新的選舉Leader是不是得到了超過半數以上伺服器的支援。如果是,那麼嘗試在200ms內接收下資料,如果沒有心資料到來說明大家已經認同這個結果。這時,設定角色然後退出選舉。
如果Sender的狀態是FOLLOWING或者LEADER
- 如果LogicalClock相同,將資料儲存早recvset,如果Sender宣稱自己是Leader,那麼判斷是不是半數以上的伺服器都選舉它,如果是設定角色並退出選舉。
- 否則,這是一條與當前LogicalClock不符合的訊息,說明在另一個選舉過程中已經有了選舉結果,於是將該選舉結果加入到OutOfElection集合中,根據OutOfElection來判斷是否可以結束選舉,如果可以也是儲存LogicalClock,更新角色,退出選舉。
具體實現
資料結構
本地訊息結構:
static public class Notification {
long leader; //所推薦的Server id
long zxid; //所推薦的Server的zxid(zookeeper transtion id)
long epoch; //描述leader是否變化(每一個Server啟動時都有一個logicalclock,初始值為0)
QuorumPeer.ServerState state; //傳送者當前的狀態
InetSocketAddress addr; //傳送者的ip地址
}
複製程式碼
網路訊息結構:
static public class ToSend {
int type; //訊息型別
long leader; //Server id
long zxid; //Server的zxid
long epoch; //Server的epoch
QuorumPeer.ServerState state; //Server的state
long tag; //訊息編號
InetSocketAddress addr;
}
複製程式碼
執行緒處理
每個Server都一個接收執行緒池和一個傳送執行緒池, 在沒有發起選舉時,這兩個執行緒池處於阻塞狀態,直到有訊息到來時才解除阻塞並處理訊息,同時每個Server都有一個選舉執行緒(可以發起選舉的執行緒擔任)。
-
接收執行緒的處理
notification: 首先檢測當前Server上所被推薦的zxid,epoch是否合法(currentServer.epoch <= currentMsg.epoch && (currentMsg.zxid > currentServer.zxid || (currentMsg.zxid == currentServer.zxid && currentMsg.id > currentServer.id))) 如果不合法就用訊息中的zxid,epoch,id更新當前Server所被推薦的值,此時將收到的訊息轉換成Notification訊息放入接收佇列中,將向對方傳送ack訊息。
ack: 將訊息編號放入ack佇列中,檢測對方的狀態是否是LOOKING狀態,如果不是說明此時已經有Leader已經被選出來,將接收到的訊息轉發成Notification訊息放入接收對佇列 -
傳送執行緒池的處理
notification: 將要傳送的訊息由Notification訊息轉換成ToSend訊息,然後傳送對方,並等待對方的回覆,如果在等待結束沒有收到對方法回覆,重做三次,如果重做次還是沒有收到對方的回覆時檢測當前的選舉(epoch)是否已經改變,如果沒有改變,將訊息再次放入傳送佇列中,一直重複直到有Leader選出或者收到對方回覆為止。
ack: 主要將自己相關資訊傳送給對方 -
選舉執行緒的處理
首先自己的epoch加1,然後生成notification訊息,並將訊息放入傳送佇列中,系統中配置有幾個Server就生成幾條訊息,保證每個Server都能收到此訊息,如果當前Server的狀態是LOOKING就一直迴圈檢查接收佇列是否有訊息,如果有訊息,根據訊息中對方的狀態進行相應的處理。
AuthFastLeaderElection選舉演算法
AuthFastLeaderElection演算法同FastLeaderElection演算法基本一致,只是在訊息中加入了認證資訊,該演算法在最新的Zookeeper中也建議棄用。
Example
下面看一個Leader選舉的例子以加深對Leader選舉演算法的理解。
- 伺服器1啟動,此時只有它一臺伺服器啟動了,它發出去的報沒有任何響應,所以它的選舉狀態一直是LOOKING狀態.
- 伺服器2啟動,它與最開始啟動的伺服器1進行通訊,互相交換自己的選舉結果,由於兩者都沒有歷史資料,所以id值較大的伺服器2勝出,但是由於沒有達到超過半數以上的伺服器都同意選舉它(這個例子中的半數以上是3),所以伺服器1,2還是繼續保持LOOKING狀態.
- 伺服器3啟動,根據前面的理論分析,伺服器3成為伺服器1,2,3中的Leader,而與上面不同的是,此時有三臺伺服器選舉了它,所以它成為了這次選舉的Leader.
- 伺服器4啟動,根據前面的分析,理論上伺服器4應該是伺服器1,2,3,4中最大的,但是由於前面已經有半數以上的伺服器選舉了伺服器3,所以它只能是Follower.
- 伺服器5啟動,同4一樣,Follower.