zookeeper領導者選舉源碼分析


基於版本3.4.13

在這裏插入圖片描述
QuorumCnxManager主要負責和其他節點數據傳輸
sendqueue:選票發送隊列,用於保存待發送的選票。
recvqueue:選票接收隊列,用於保存接收到的外部投票。
WorkerReceiver:選票接收器。其會不斷地從QuorumCnxManager中獲取其他服務器發來的選舉消息,並將其轉換成一個選票,然後保存到recvqueue中,在選票接收過程中,如果發現該外部選票的選舉輪次小於當前服務器的,那麼忽略該外部投票,同時立即發送自己的內部投票。
WorkerSender:選票發送器,不斷地從sendqueue中獲取待發送的選票,並將其傳遞到底層QuorumCnxManager中。
recvQueue:當前節點接受信息的列隊
senderWorkerMap:每臺節點對應的senderworker,<Long, SendWorker>,Long爲服務器myid,SendWorker用於從queueSendMap存的Queue取數據發送給其他節點的,是個線程

注意圖裏的recvqueue(FastLeaderElection類中)隊列和recvQueue(QuorumCnxManager類中)是不一樣的。

代碼入口

QuorumPeerMain.main —> initializeAndRun方法—>runFromConfig方法裏 quorumPeer.start() 啓動QuorumPeer這個線程
QuorumPeer類:

    @Override
    public synchronized void start() {
        // 加載數據
        loadDataBase();
        // 開啓讀取數據線程,取客戶端數據
        cnxnFactory.start();
        // 進行領導者選舉
        startLeaderElection();
        super.start();
    }

leader選舉細節

服務器狀態

代碼位於QuorumPeer的run方法
服務器有四種狀態,分別是LOOKING、FOLLOWING、LEADING、OBSERVING。

  • LOOKING:尋找leader狀態。當服務器處於該狀態時,它會認爲當- 前集羣中沒有leader,因此需要進入leader選舉狀態。
  • FOLLOWING:跟隨者狀態。表明當前服務器角色是follower。
  • LEADING:領導者狀態。表明當前服務器角色是leader。
  • OBSERVING:觀察者狀態。表明當前服務器角色是observer。

投票數據結構

每個投票中包含了兩個最基本的信息,所推舉服務器的SID和ZXID,投票(Vote)在Zookeeper中包含字段如下

  • id:被推舉的Leader的SID。
  • zxid:被推舉的Leader事務ID。
  • electionEpoch:邏輯時鐘,用來判斷多個投票是否在同一輪選舉週期中,該值在服務端是一個自增序列,每次進入新一輪的投票後,都會對該值進行加1操作。
  • peerEpoch:被推舉的Leader的epoch。
  • state:當前服務器的狀態。

FastLeaderElection:選舉算法

在3.4.0後的Zookeeper的版本只保留了FastLeaderElection選舉算法。
在這裏插入圖片描述

上圖展示了FastLeaderElection模塊是如何與底層網絡I/O進行交互的。Leader選舉的基本流程如下

  1. 自增選舉輪次。Zookeeper規定所有有效的投票都必須在同一輪次中,在開始新一輪投票時,會首先對logicalclock進行自增操作。
  2. 初始化選票。在開始進行新一輪投票之前,每個服務器都會初始化自身的選票,並且在初始化階段,每臺服務器都會將自己推舉爲Leader。
  3. 發送初始化選票。完成選票的初始化後,服務器就會發起第一次投票。Zookeeper會將剛剛初始化好的選票放入sendqueue中,由發送器WorkerSender負責發送出去。
    FastLeaderElection類的lookForLeader方法
 public Vote lookForLeader() throws InterruptedException {
	...
	try {
            //投票箱<Long, Vote> Long:其他節點sid Vote:其他節點投的票
            HashMap<Long, Vote> recvset = new HashMap<Long, Vote>();
            HashMap<Long, Vote> outofelection = new HashMap<Long, Vote>();
            int notTimeout = finalizeWait;
            synchronized(this){
                // 時鐘+1
                logicalclock.incrementAndGet();
                // 更新提議(投票),包含(myid,LastZxid, epoch), 投自己
                updateProposal(getInitId(), getInitLastLoggedZxid(), getPeerEpoch());
            }
            // 把投票發送出去,寫到sendqueue
            sendNotifications();
	...
}
  1. 接收外部投票。每臺服務器會不斷地從recvqueue隊列中獲取外部選票。如果服務器發現無法獲取到任何外部投票,那麼就會立即確認自己是否和集羣中其他服務器保持着有效的連接,如果沒有連接,則馬上建立連接,如果已經建立了連接,則再次發送自己當前的內部投票。

  2. 判斷選舉輪次。在發送完初始化選票之後,接着開始處理外部投票。在處理外部投票時,會根據選舉輪次來進行不同的處理。

    • 外部投票的選舉輪次大於內部投票。若服務器自身的選舉輪次落後於該外部投票對應服務器的選舉輪次,那麼就會立即更新自己的選舉輪次(logicalclock),並且清空所有已經收到的投票,然後使用初始化的投票來進行PK以確定是否變更內部投票。最終再將內部投票發送出去。
    • 外部投票的選舉輪次小於內部投票。若服務器接收的外選票的選舉輪次落後於自身的選舉輪次,那麼Zookeeper就會直接忽略該外部投票,不做任何處理,並返回步驟4。
    • 外部投票的選舉輪次等於內部投票。此時可以開始進行選票PK。
  3. 選票PK。在進行選票PK時,符合任意一個條件就需要變更投票。

    • 若外部投票中推舉的Leader服務器的選舉輪次大於內部投票,那麼需要變更投票。

    • 若選舉輪次一致,那麼就對比兩者的ZXID,若外部投票的ZXID大,那麼需要變更投票。

    • 若兩者的ZXID一致,那麼就對比兩者的SID,若外部投票的SID大,那麼就需要變更投票。

  4. 變更投票。經過PK後,若確定了外部投票優於內部投票,那麼就變更投票,即使用外部投票的選票信息來覆蓋內部投票,變更完成後,再次將這個變更後的內部投票發送出去。

  5. 選票歸檔。無論是否變更了投票,都會將剛剛收到的那份外部投票放入選票集合recvset中進行歸檔。recvset用於記錄當前服務器在本輪次的Leader選舉中收到的所有外部投票(按照服務隊的SID區別,如{(1, vote1), (2, vote2)…})。

  6. 統計投票。完成選票歸檔後,就可以開始統計投票,統計投票是爲了統計集羣中是否已經有過半的服務器認可了當前的內部投票,如果確定已經有過半服務器認可了該投票,則終止投票。否則返回步驟4。

  7. 更新服務器狀態。若已經確定可以終止投票,那麼就開始更新服務器狀態,服務器首選判斷當前被過半服務器認可的投票所對應的Leader服務器是否是自己,若是自己,則將自己的服務器狀態更新爲LEADING,若不是,則根據具體情況來確定自己是FOLLOWING或是OBSERVING。

以上10個步驟就是FastLeaderElection的核心,其中步驟4-9會經過幾輪循環,直到有Leader選舉產生。

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