ZooKeeper 選舉過程 源碼

  • 參考:https://www.bilibili.com/video/BV1tt41137sT?p=4
  • 視頻中帶註釋的源碼:https://github.com/boomblog/zookeeper-vip1/blob/master/src/java/main/org/apache/zookeeper/server/quorum/QuorumPeer.java
  • 啓動
    • 傳參
      • 腳本啓動的時候 .sh 發現調用了一個類 傳入配置文件參數 就啓動了
      • 我們可以通過idea 加入參數,也能啓動。
    • 找夥伴
      • 一開始先啓動server1,server1會嘗試連接server2和server3,結果都失敗,然後會重試
      • 然後我們來個客戶端連接的話,因爲沒有leader,會連上就斷開連上就斷開,不斷重試,直到有leader。
    • 加載數據
      • 讀入我們的配置
      • 從硬盤中把持久化的內容讀到內存裏
    • 開啓socket : cnxnFactory.start()
      • 使用NIO
    • 開啓領導選舉
      • 選舉傳輸層(負責傳輸選票)QuorumCnxManager
        • 每個服務器要向其他服務器發送選票
          • 每個服務器對應一個隊列 queueSendMap,裏面放發給這個服務器的選票
        • 每個服務器要從其他服務器接收選票
          • ArrayBlockingQueue<Message> recvQueue
      • 創建個listener,接收別的小夥伴的請求(創建個新線程)
    • 真正開始選舉:super.start() 就是 QuorumPeer.run(): FastLeaderElection
      • 先投給自己
      • 接受其他小夥伴的選票
        • 如果拿不到別人的選票(取隊列爲空),纔會去建立連接(connectAll)
          • 如果兩個server 相互主動連接對方,怎麼辦?
            • 拿到對方的id,和自己的id比較,如果對方的大,關掉連接。
            • 所以永遠是小的做爲server端,大的作爲客戶端建立連接。
      • pk
        • 排序順序:epoch zxid server_id
        • 選票其實也是長這樣(epoch, zxid, server_id)
      • 投票
        • 從server n接收到一個選票,將其更新成贏了的的選票(可能是他,也可能是我自己)放進以server_id爲key的map中(投票箱)
      • 統計 
        • 統計過半數
  •  
  • 跟上面的流程對應的代碼:
  • org.apache.zookeeper.server.quorum.QuorumPeerMain#runFromConfig​ 
    • quorumPeer.start();​開啓線程
      • org.apache.zookeeper.server.quorum.QuorumPeer#run。MainLoop,根據status做不同的事情
        • case LOOKING(剛啓動就是這個,正在尋找老大) 
          • org.apache.zookeeper.server.quorum.QuorumPeer#startLeaderElection
            • currentVote = new Vote(myid, getLastLoggedZxid(), getCurrentEpoch());
              先投自己
            • this.electionAlg = createElectionAlgorithm(electionType);
              默認是用3:fastElection。然後會啓動兩個線程,瘋狂從兩個隊列(WorkerSender、WorkerReceiver​)poll
              • WorkerSender:就是把選票發出去
              • WorkerReceiver:接受選票並處理
          • setCurrentVote(makeLEStrategy().lookForLeader());
            看下要不要改票。/** * Starts a new round of leader election. Whenever our QuorumPeer changes its state to LOOKING, this method is invoked, and it  sends notifications to all other peers. */開始新的一輪選舉,當QuorumPeer改變狀態爲LOOKING,就會調用這個方法,然後會發通知給所有其他小夥伴
            • HashMap<Long, Vote> recvset
              收到的選票,也就是本地投票箱
            • HashMap<Long, Vote> outofelection 
            • 選票上寫上自己
            • sendNotifications(); 選票發送。實際上是填好選票放到發送隊列裏。別的線程來負責發送。
            • while ((self.getPeerState() == ServerState.LOOKING) &&(!stop))
              只要還在LOOKING狀態,而且程序沒有停止,就一直循環下面邏輯:​
              • Notification n = recvqueue.poll
                接收隊列拖出一個通知
              • 如果是空就繼續發通知sendNotification
                • 如果有一個隊列都清空了(haveDelivered)(queueSendMap),就再sendNotification【sendqueue(應用層,只有一個隊列)和queueSendMap(傳輸層,分不同的小夥伴有不同的隊列)】
                • 否則,嘗試連接所有其他小夥伴
              • 如果收到有效選票
              • switch (n.state) 
                • case LOOKING:
                  收到的選票也在LOOKING​
                  • 如果對方的epoch比較大
                    • 那就更新自己存的
                    • 清空收到的選票recvset
                    • 按照規則排序,對方選票牛皮則跟着對方投,否則還按自己的投
                      epoch > zxid > serverid
                  • 如果對方的epoch比自己小,可能是莫名其妙哪出問題了,打debug日誌然後break循環
                  • 對方的epoch跟我一樣
                    • 那也按照規則排序,誰牛皮就按誰的投
                  • 把對方的選票放進投票箱recvset
                  • 如果termPredicate返回true:從recvqueue中清空選票,只要找到一張更牛皮的選票就把票放回去繼續循環。如果是真的隊列空了,就結束了,看看是不是自己當了老大,是的話就把state改成LEADING,否則改成FOLLOWING
                    /** * Termination predicate. Given a set of votes, determines if have * sufficient to declare the end of the election round. 瞅一眼投票箱,看看是不是足夠宣告投票結束。
                    • 其中一種校驗方法是獲得過半數的選票
                • case FOLLOWING ||LEADING:
        • case FOLLOWING
        • case LEADING
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章