天天看點

Zookeeper選舉整體流程源碼解析一  Zookeeper選舉流程概述 二 選舉源碼概述三 總結

一  Zookeeper選舉流程概述

     Zookeeper選舉機制分為兩種:第一次啟動和非第一次啟動。假設有5個節點,如圖1.1所示:

Zookeeper選舉整體流程源碼解析一  Zookeeper選舉流程概述 二 選舉源碼概述三 總結

 圖1.1 ZK叢集

 1.1 第一次啟動

     (1)伺服器1啟動,發起選舉。伺服器1投自己一票,此時伺服器1有一票,未超過半數以上票數,選舉無法完成,伺服器保持狀态為LOOKING;

    (2)伺服器2啟動,再次發起選舉。伺服器1和伺服器2分别半先投給自己1票,伺服器1和伺服器2會通信,此時伺服器1發現伺服器2的myid比自己大,是以會将自己的票給伺服器2。此時伺服器1票數為0,伺服器票數為2。伺服器2的票數沒有達到半數以上,選舉無法完成,伺服器1和2狀态為LOOKING;

    (3)伺服器3啟動,發起一次選舉。最終服務1會有0票,伺服器2會有0票,伺服器3會有3票,此時伺服器3的票數超過半數,是以伺服器3當選為leader。伺服器1和伺服器2的狀态變為FOLLOWING,伺服器3的狀态變為LEADING;

    (4)随後伺服器4和伺服器5啟動,此時已經有leader了,不再進行選舉了,伺服器4和伺服器5更改狀态為FOLLOWING。

1.2 非第一次啟動 

     當叢集中的一台伺服器無法和leader保持連接配接時,會進入leader選舉,而此時,叢集會有以下兩種狀态:

    叢集中已經存在leader:針對這種情況,該節點試圖去選擇leader時,會被其他節點告知目前伺服器的leader資訊。是以,該伺服器僅需要和leader重建立立連接配接并同步狀态即可。

    叢集中已經不存在leader:假設叢集由5個節點組成,SID分别為1,2,3,4,5;ZXID分别為9,9,8,7,6,EPOCH均為1,且此時SID為3的伺服器為leader。突然,當3和5伺服器發生故障時,會觸發leader選舉。選舉規則為:EPOCH大的直接勝出,如果EPOCH相同,則ZXID大的勝出,如果ZXID相同,則SID大的勝出。

SID:伺服器ID,用來辨別一台伺服器,ID不能重複,且和myid一緻

ZXID:事務ID,用來辨別一次伺服器狀态的變更。在某一時刻,叢集中的每台機器的ZXID不一定一緻

EPOCH:每個leader任期的代号。每投完一次票該資料會增加

 二 選舉源碼概述

    源碼中的選舉整體流程如圖2.1所示:

Zookeeper選舉整體流程源碼解析一  Zookeeper選舉流程概述 二 選舉源碼概述三 總結

 圖2.1 源碼選舉概述圖

    整個選舉過程主要由兩個類完成,FastLeaderElection和QuorumCnManager。FastLeaderElection負責接收選票和發送選票; QuorumCnManager負責管理節點之間的選舉通信,負責選擇票數的傳遞。

2.1 選舉準備源碼解析

Zookeeper選舉整體流程源碼解析一  Zookeeper選舉流程概述 二 選舉源碼概述三 總結

圖2.2 

    選舉代碼的入口在QuorumPeerMain中的runFromConfig方法中,如圖2.2所示“在完成伺服器啟動的初始化後,會執行quorumPeer.start(),進入該方法,執行startLeaderElection()方法進入選舉流程。

    如果目前節點的狀态為LOOKING(getPeerState() == ServerState.LOOKING),會建立一個Vote類,該類中有id,zxid,electionEpoch等選舉關鍵資訊。随後,如圖2.3所示,會通過調用createElectionAlgorithm(electionType)來建立QuorumCnxManager,該類維護了一個接收隊列,recvQueue,該隊列接收其他節點的投票;也維護了一個queueSendMap,該map的鍵為伺服器ID,值為ArrayBlockingQueue隊列,該隊列包含了鍵值所指向的伺服器的選票;該類還維護了一個senderWorkerMap,該map存儲了一個發送線程,負責将投票發送到其他節點,還存儲了一個接收線程,負責接收其他節點的投票。

   如圖2.3所示,監聽類QuorumCnxManager.Listener繼承了線程類,在覆寫的run方法中,隻要未shutdown,會一直等待其他節點發送資料。

Zookeeper選舉整體流程源碼解析一  Zookeeper選舉流程概述 二 選舉源碼概述三 總結

 圖 2.3

 2.2 選舉執行源碼解析

Zookeeper選舉整體流程源碼解析一  Zookeeper選舉流程概述 二 選舉源碼概述三 總結

 圖2.4

     執行super.start(),就是執行QuorumPeer.java類中覆寫的run()方法,當zookeeper啟動後,首先都是Looking狀态,通過選舉讓其中一台伺服器成為Leader,其他的成為Follower 

Zookeeper選舉整體流程源碼解析一  Zookeeper選舉流程概述 二 選舉源碼概述三 總結

 圖2.5

     通過調用setCurrentVote(makeLEStrategy().lookForLeader())進行選舉,進入lookForLeader()方法,這裡進入FastLeaderElection實作的lookForLeader方法,如圖2.6所示:

Zookeeper選舉整體流程源碼解析一  Zookeeper選舉流程概述 二 選舉源碼概述三 總結

圖2.6 

     recvset儲存了每一個伺服器給該節點的合法有效投票,recvset的鍵為伺服器id,value為其他節點給我的投票資訊。notTimeout為一次選舉的最大等待時間,預設是0.2s。繼續往下看,如圖2.7所示:

Zookeeper選舉整體流程源碼解析一  Zookeeper選舉流程概述 二 選舉源碼概述三 總結

 圖2.7

    每發起一輪選舉,都會使logicallock加1,然後調用updateProposal()更新選票資訊 。更新完選票資訊後,調用sendNotifications()廣播選票。如圖2.8所示:

Zookeeper選舉整體流程源碼解析一  Zookeeper選舉流程概述 二 選舉源碼概述三 總結

 圖2.8

    通過for循環周遊投票參與者,給每台伺服器發送選票(就是給SID比自己大的伺服器投票),通過new 一個ToSend對象建立發送選票資訊,然後将該資訊放入隊列sendqueue中。該隊列中的資訊是由WorkerSender(WorkerSender是FastLeaderElection的一個内部類)線程來負責發送的,WorkerSender的run方法如圖2.9所示: 

Zookeeper選舉整體流程源碼解析一  Zookeeper選舉流程概述 二 選舉源碼概述三 總結

 圖 2.9

     通過sendqueue.poll從隊列中擷取要發送的選票,然後将選票資訊傳給process方法進行處理,如圖2.10: 

Zookeeper選舉整體流程源碼解析一  Zookeeper選舉流程概述 二 選舉源碼概述三 總結

 圖2.10

   通過manager.toSend發送資訊,manager為類QuorumCnManager,負責管理節點之間的選舉通信,負責選擇票數的傳遞。 toSend()如圖2.11所示:

Zookeeper選舉整體流程源碼解析一  Zookeeper選舉流程概述 二 選舉源碼概述三 總結

圖2.12 

    首先通過this.mySid==id判斷是不是自己給自己投票,如果是就通過addToRecvQueue将投票資訊 放進自己的RecvQueue。如果是發送給其他伺服器的選票,如果其他伺服器的隊列消息已經存現,就将選票資訊放入oldq中,否則,則放入新的bq隊列中。最後通過connectOne(sid)将消息發送出去。

    在connectOne(sid)中會調用conncectOne(long sid, InetSocketAddress electionAddr)與其他節點建立連接配接,如圖2.13所示:

Zookeeper選舉整體流程源碼解析一  Zookeeper選舉流程概述 二 選舉源碼概述三 總結

 圖2.14

     如圖2.15所示,在conncectOne(long sid, InetSocketAddress electionAddr)方法中與其他節點建立連接配接,然後通過initiateConnection(sock, id)處理連接配接。                       

Zookeeper選舉整體流程源碼解析一  Zookeeper選舉流程概述 二 選舉源碼概述三 總結

 圖2.15

       在initiateConnection(sock, id)方法中調用startConnection(sock, id),并在該方法中建立并啟動了發送器線程和接收器線程,如圖2.16所示:

Zookeeper選舉整體流程源碼解析一  Zookeeper選舉流程概述 二 選舉源碼概述三 總結
Zookeeper選舉整體流程源碼解析一  Zookeeper選舉流程概述 二 選舉源碼概述三 總結

 圖2.16

   有一處重要的判斷if(sid>self.getId()) ,如果要發送的節點的id比自己要大,就直接關閉自己的用戶端,意思就是不參與選舉了。通過new SendWorker和RecvWorker建立發送和接收線程,并調用start方法啟動發送和接收線程。

    如圖2.17所示,SendWorker會在run方法中一直循環從發送隊列SendQueue中,擷取發送消息,并調用send(b)進行發送。

Zookeeper選舉整體流程源碼解析一  Zookeeper選舉流程概述 二 選舉源碼概述三 總結

 圖2.17

     同樣的,如圖2.18.在RecvWorker的run方法中,也會一直從輸入流中接收消息,調用addToRecvQueue将消息添加的發送隊列中,後續會将其他節點給我的票選發送的我這。 

Zookeeper選舉整體流程源碼解析一  Zookeeper選舉流程概述 二 選舉源碼概述三 總結

 圖2.18

   如圖2.19,從 addToRecvQueue(Message msg)方法中,調用了recvQueue.add(msg)将其他節點給我的票選資訊加入隊列。

Zookeeper選舉整體流程源碼解析一  Zookeeper選舉流程概述 二 選舉源碼概述三 總結

 圖2.19

     最後回到FastLeaderElection類中查找WorkerReceiver線程,如圖2.20所示,調用manager.pollRecvQueue方法從recvQueue中擷取其他節點發送給我的票選資訊。

    至此,選舉流程的源碼分析到此結束了, 本文章隻分析了一個整體流程的源碼,可以幫助大家對選舉流程有個整體的把握。有些細節沒有去分析,有興趣的讀者可以自己下載下傳源碼分析

三 總結

    選舉源碼流程總結如圖3.1所示:

Zookeeper選舉整體流程源碼解析一  Zookeeper選舉流程概述 二 選舉源碼概述三 總結

​圖3.1 

         附Zookeeper入門學習視訊:

https://www.bilibili.com/video/BV1to4y1C7gw