ZooKeeper快速領導選舉(Fast Leader Election)機制解析

前言

假期馬上就要過去了,還是寫點什麼找找狀態比較好。翻看之前的文章,發現自己說過不少ZooKeeper的應用,但還沒有真正涉及到它的原理,那麼本文就找個切入點來聊聊吧。

Leader選舉

衆所周知,ZK是典型的Leader-Follower架構的分佈式框架,通過ZooKeeper原子廣播(ZooKeeper Atomic Broadcast, ZAB)協議來保證最終一致性。只有Leader能處理寫請求,而Leader和Follower都能處理讀請求。

一個ZK集羣的示意圖如下。注意圖中沒有Observer節點(即無投票權的Follower節點),無傷大雅。

既然Leader在整個ZK集羣中處於中心地位,那麼在ZK集羣啓動時,需要由所有節點共同選舉出Leader節點,才能正常提供服務。同理,如果當前Leader失敗,也需要儘快推舉出新的Leader節點,避免處於“羣龍無首”的狀態。ZAB協議的第0個階段就是Leader選舉,而具體到ZK的實現上,稱爲快速領導選舉(Fast Leader Election)機制,如下圖所示。

ZAB協議的恢復和廣播階段今後再提,本文重點分析Fast Leader Election的相關內容。作爲預備,先介紹ZK節點狀態及與選舉相關的信息。

ZK節點狀態

一個ZK節點可能處於以下4種狀態之一,在源碼中以QuorumPeer#ServerState枚舉來定義。

  • LOOKING:不確定Leader的“尋找”狀態,即當前節點認爲集羣中沒有Leader,進而發起選舉;
  • LEADING:“領導”狀態,即當前節點就是Leader,並維護與Follower和Observer的通信;
  • FOLLOWING:“跟隨”狀態,即當前節點是Follower,且正在保持與Leader的通信;
  • OBSERVING:“觀察”狀態,即當前節點是Observer,且正在保持與Leader的通信,但是不參與Leader選舉。

與選舉相關的信息

這些信息體現在FastLeaderElection#Notification、FastLeaderElection#ToSend和Vote等數據結構中,看官可自行參考源碼。

  • electionEpoch:“選民”的選舉輪次,在每個節點中以邏輯時鐘logicalclock的形式存儲。每發起一輪新的選舉,該值會加1。若節點重啓,此值會歸零。
  • sid:“選民”自己的服務器ID,是一個正整數,由各個ZK實例中的$dataDir/myid指定。
  • state:“選民”的狀態
  • votedLeaderSid:這一票推選的“候選人”的服務器ID。在代碼中直接命名爲leader,爲了防止混淆,這裏稍作更改。
  • votedLeaderZxid:這一票推選的“候選人”的事務ID。所謂事務ID即寫操作的proposal ID,其高32位是Leader紀元值,低32位是當前Leader紀元下的操作序號,亦即zxid肯定是單調遞增的。在代碼中直接命名爲zxid,爲了防止混淆,這裏稍作更改。
  • recvset:“選民”的票箱,其中存儲有自己的和其他節點的選票。注意,每張選票都包含上述的electionEpoch、sid、state、leader和zxid信息,並且票箱中都只會記錄每個“選民”的最近一次投票信息

選舉流程

Fas tLeader Election算法的主體位於FastLeaderElection#lookForLeader方法中,代碼比較長,因此不貼出來了,只用文字敘述流程。

首先,自增本地的邏輯時鐘logicalclock。

接下來給自己投一票(即該票中包含自己的sid、zxid等信息),並將該選票廣播給其他節點。

投出這一票後,只要當前服務器處於LOOKING狀態,就會循環執行收取其他選票、更新並廣播自己的選票、計算投票結果的操作,直到可以確定Leader爲止。具體敘述如下。

  • 如果對方的狀態也是LOOKING,說明兩方都處於選舉流程中(可能是集羣剛剛啓動,或者Leader掉線)——

    • 若對方選票中的electionEpoch等於當前的logicalclock,說明當前節點與對方處於同一選舉輪次,需要將對方的選票與自己剛纔投出的票進行對比,看哪個候選Leader更優。規則是先比較事務ID(votedLeaderZxid),再比較服務器ID(votedLeaderSid),值較大的候選Leader更優。然後投更優的候選Leader一票,並廣播出去。
    • 若對方選票中的electionEpoch大於當前的logicalclock,說明當前節點的選舉輪次已經滯後。此時將logicalclock更新爲該選票的electionEpoch,並清空recvset(因爲上一輪的選票已經沒用了)。然後按照上一條的規則對比選票,並投出自己的新選票。
    • 若對方選票中的electionEpoch小於當前的logicalclock,說明是對方滯後,忽略這一票。
    • 將自己的選票和其他人的選票放入recvset中,並進行計票。如果收到了所有節點的選票,則直接認爲選舉結束,根據票數修改自己的狀態爲LEADING或FOLLOWING;如果沒有收到所有選票,但已經過半(即滿足Quorum原則),那麼就等待一個較短的時期(默認200ms),如果選舉的結果沒有改變,則仍然認爲選舉已經結束,修改狀態。這裏我們只考慮數量,不考慮節點權重的問題。
  • 如果對方的狀態是LEADING/FOLLOWING,說明對方不處於選舉流程中(可能是當前節點因故重啓)——

    • 若對方選票中的electionEpoch等於當前的logicalclock,說明選舉結果已經出來了,將它們放入recvset。特別地,如果收到了對方稱自己處於LEADING狀態的票,則進行計票並檢查該節點是否得到了過半數的支持。如是,說明Leader有效,修改自身狀態爲FOLLOWING並退出選舉。
    • 若對方選票中的electionEpoch不等於當前的logicalclock,說明在另一場選舉中已經有了結果,只需聽從該結果即可。這時需要將logicalclock直接設爲對方的electionEpoch值,並將其他節點的選票放入一個旁路的outofelection集合並進行計票(顧名思義,這個集合只用來表徵選舉結果,不用於實際的選舉流程),根據得出的結果修改自身狀態,再結束選舉。

可見,Fast Leader Election流程的本質就是每個節點通過不斷做出最優的選擇並進行廣播,最終使所有節點對Leader和Follower角色的認知收斂到一致。

下面通過畫圖來演示上文所述的選舉流程。

選舉流程示例

  • 集羣啓動時
  • Follower失敗重啓時
  • Leader失敗重啓時的情況,就留給看官自行思考吧(因爲筆者要去洗衣服了hhhhhhhhhh

The End

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