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

圖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所示:

圖2.1 原始碼選舉概述圖
整個選舉程序主要由兩個類完成,FastLeaderElection和QuorumCnManager,FastLeaderElection負責接收選票和發送選票; QuorumCnManager負責管理節點之間的選舉通信,負責選擇票數的傳遞,
2.1 選舉準備原始碼決議

圖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,會一直等待其他節點發送資料,

圖 2.3
2.2 選舉執行原始碼決議

圖2.4
執行super.start(),就是執行QuorumPeer.java類中覆寫的run()方法,當zookeeper啟動后,首先都是Looking狀態,通過選舉讓其中一臺服務器成為Leader,其他的成為Follower 
圖2.5
通過呼叫setCurrentVote(makeLEStrategy().lookForLeader())進行選舉,進入lookForLeader()方法,這里進入FastLeaderElection實作的lookForLeader方法,如圖2.6所示:

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

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

圖2.8
通過for回圈遍歷投票參與者,給每臺服務器發送選票(就是給SID比自己大的服務器投票),通過new 一個ToSend物件創建發送選票資訊,然后將該資訊放入佇列sendqueue中,該佇列中的資訊是由WorkerSender(WorkerSender是FastLeaderElection的一個內部類)執行緒來負責發送的,WorkerSender的run方法如圖2.9所示:

圖 2.9
通過sendqueue.poll從佇列中獲取要發送的選票,然后將選票資訊傳給process方法進行處理,如圖2.10:

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

圖2.12
首先通過this.mySid==id判斷是不是自己給自己投票,如果是就通過addToRecvQueue將投票資訊 放進自己的RecvQueue,如果是發送給其他服務器的選票,如果其他服務器的佇列訊息已經存現,就將選票資訊放入oldq中,否則,則放入新的bq佇列中,最后通過connectOne(sid)將訊息發送出去,
在connectOne(sid)中會呼叫conncectOne(long sid, InetSocketAddress electionAddr)與其他節點建立連接,如圖2.13所示:

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

圖2.15
在initiateConnection(sock, id)方法中呼叫startConnection(sock, id),并在該方法中創建并啟動了發送器執行緒和接收器執行緒,如圖2.16所示:


圖2.16
有一處重要的判斷if(sid>self.getId()) ,如果要發送的節點的id比自己要大,就直接關閉自己的客戶端,意思就是不參與選舉了,通過new SendWorker和RecvWorker創建發送和接收執行緒,并呼叫start方法啟動發送和接收執行緒,
如圖2.17所示,SendWorker會在run方法中一直回圈從發送佇列SendQueue中,獲取發送訊息,并呼叫send(b)進行發送,

圖2.17
同樣的,如圖2.18.在RecvWorker的run方法中,也會一直從輸入流中接收訊息,呼叫addToRecvQueue將訊息添加的發送佇列中,后續會將其他節點給我的票選發送的我這,

圖2.18
如圖2.19,從 addToRecvQueue(Message msg)方法中,呼叫了recvQueue.add(msg)將其他節點給我的票選資訊加入佇列,

圖2.19
最后回到FastLeaderElection類中查找WorkerReceiver執行緒,如圖2.20所示,呼叫manager.pollRecvQueue方法從recvQueue中獲取其他節點發送給我的票選資訊,
至此,選舉流程的原始碼分析到此結束了, 本文章只分析了一個整體流程的原始碼,可以幫助大家對選舉流程有個整體的把握,有些細節沒有去分析,有興趣的讀者可以自己下載原始碼分析
三 總結
選舉原始碼流程總結如圖3.1所示:
?圖3.1
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/299942.html
標籤:其他
