Zookeeper原始碼分析-Zookeeper Leader選舉演算法

核動力蝸牛發表於2018-09-09

本文首發於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以後的版本廢棄。

選舉演算法流程如下:

  1. 選舉執行緒首先向所有Server發起一次詢問(包括自己);
  2. 選舉執行緒收到回覆後,驗證是否是自己發起的詢問(驗證xid是否一致),然後獲取對方的id(myid),並儲存到當前詢問物件列表中,最後獲取對方提議的leader相關資訊(id,zxid),並將這些資訊儲存到當次選舉的投票記錄表中;
  3. 收到所有Server回覆以後,就計算出zxid最大的那個Server,並將這個Server相關資訊設定成下一次要投票的Server;
  4. 執行緒將當前zxid最大的Server設定為當前Server要推薦的Leader,如果此時獲勝的Server獲得多數Server票數, 設定當前推薦的leader為獲勝的Server,將根據獲勝的Server相關資訊設定自己的狀態,否則,繼續這個過程,直到leader被選舉出來。

leader-election

通過流程分析我們可以得出:要使Leader獲得多數Server的支援,則Server總數必須是奇數2n+1,且存活的Server的數目不得少於n+1.

異常問題的處理:

  1. 選舉過程中,Server的加入
    當一個Server啟動時它都會發起一次選舉,此時由選舉執行緒發起相關流程,那麼每個 Serve r都會獲得當前zxi d最大的哪個Serve r是誰,如果當次最大的Serve r沒有獲得n/2+1 個票數,那麼下一次投票時,他將向zxid最大的Server投票,重複以上流程,最後一定能選舉出一個Leader。
  2. 選舉過程中,Server的退出
    只要保證n/2+1個Server存活就沒有任何問題,如果少於n/2+1個Server 存活就沒辦法選出Leader。
  3. 選舉過程中,Leader死亡
    當選舉出Leader以後,此時每個Server應該是什麼狀態(FLLOWING)都已經確定,此時由於Leader已經死亡我們就不管它,其它的Fllower按正常的流程繼續下去,當完成這個流程以後,所有的Fllower都會向Leader傳送Ping訊息,如果無法ping通,就改變自己的狀為(FLLOWING ==> LOOKING),發起新的一輪選舉。
  4. 選舉完成以後,Leader死亡
    處理過程同上。
  5. 雙主問題
    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,更新角色,退出選舉。

fast-leader-election

具體實現

資料結構

本地訊息結構:

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. 伺服器1啟動,此時只有它一臺伺服器啟動了,它發出去的報沒有任何響應,所以它的選舉狀態一直是LOOKING狀態.
  2. 伺服器2啟動,它與最開始啟動的伺服器1進行通訊,互相交換自己的選舉結果,由於兩者都沒有歷史資料,所以id值較大的伺服器2勝出,但是由於沒有達到超過半數以上的伺服器都同意選舉它(這個例子中的半數以上是3),所以伺服器1,2還是繼續保持LOOKING狀態.
  3. 伺服器3啟動,根據前面的理論分析,伺服器3成為伺服器1,2,3中的Leader,而與上面不同的是,此時有三臺伺服器選舉了它,所以它成為了這次選舉的Leader.
  4. 伺服器4啟動,根據前面的分析,理論上伺服器4應該是伺服器1,2,3,4中最大的,但是由於前面已經有半數以上的伺服器選舉了伺服器3,所以它只能是Follower.
  5. 伺服器5啟動,同4一樣,Follower.

參考資料

blog.csdn.net/xhh198781/a…

blog.cnsolomo.com/ld/liunx/ng…

blog.sina.com.cn/s/blog_3fe9…

blog.csdn.net/xhh198781/a…

csrd.aliapp.com/?p=162

codemacro.com/2014/10/19/…

相關文章