文章目錄
一、前言
前面已經介紹過Zookeeper的基礎知識以及ZAB協議的一些概念,其中ZAB協議中關鍵性的發現步驟,也就是Leader選舉機制是如何完成的,在這裏簡單介紹一下。
二、Leader選舉基本概念
集羣服務節點狀態 State:
- LOOKING:尋找Leader節點狀態,該狀態下無Leader節點,觸發Leader選舉。
- FOLLOWING:跟隨者狀態,表明該節點爲Follower。
- LEADING:領導狀態,表明該節點爲Leader節點。
- OBSERVING:觀察者狀態,表明該節點爲observer節點。
關於各個節點的角色,在之前ZAB協議中已經介紹,這裏不過多介紹。
選票模型 Vote
sid: ServerID表明爲被推舉的Leader的唯一標識,等同於MyId。
zxid: 表明被推舉的Leader事務ID,也就是服務提供期間內,執行數據更新事務操作的自增ID。
electionEpoch: 邏輯時鐘,表明爲一次選舉的週期的id,每次新的選舉該值自增+1,用來判斷選舉投票是否爲同一選舉輪次。
peerEpoch: 被推舉Leader節點的選舉輪次Epoch。
state: 當前服務節點的狀態
選舉網絡通信管理器 QuorumCnxManager
每臺機器在啓動時,都會創建一個QuorumCnxManager來負責管理節點之間的選舉投票通信。
QuorumCnxManager內部維護了一系列隊列用來管理待發送的消息和收到的消息以及消息的發送器,並且負責管理與其他所有節點通信連接(socket)。
QuorumCnxManager維護的隊列與集合:
1、消息發送隊列(queueSendMap):爲其他所有節點分別維護一個消息發送隊列,隊列之間互不影響。
2、消息接受隊列(recvQueue):一個統一的消息接收隊列,負責保存接收到的消息。
3、發送器集合(senderWorkerMap): 爲其他節點分配一個消息發送器,負責消息的發送。
4、最後一條發送的消息 (lastMessageSent):存儲最近發送的一條消息,如果發送隊列爲空,則取出該消息再次發送。
QuorumCnxManager管理通信連接:
爲了節點之間能夠互相進行選舉的通信,所以所有節點之間都要兩兩建立連接,QuorumCnxManager創建一個ServerSocket監聽3888通信端口(選舉通信端口),然後持續接收其他節點的連接建立請求。
爲避免連接重複建立,zk中規定只有sid較大的向sid較小的創建連接請求,當收到連接請求時,判斷sid比自身小,則斷開本次連接,然後自己主動發起連接建立請求。一旦連接建立,就會根據遠程服務器的SID來創建相應的消息發送器SendWorker和消息接收器RecvWorker,並啓動他們。
QuorumCnxManager消息發送器和消息接收器:
由於創建連接時爲每個遠程服務器分別創建了SendWorker和RecvWorker,所以對於消息的接收,直接由RecvWorker從TCP連接中取出放入消息接收隊列recvQueue即可。
對於消息的發送,只要不斷的從該遠程服務器的消息發送隊列queueSendMap中取出一條消息發送即可,如果發送隊列爲空,則獲取最後一次發送的信息再次發送,這裏爲了防止遠程服務器接收消息前或接收消息後服務器掛掉後導致消息沒有處理。Zookeeper能夠保證接收方在處理消息時,會對重複消息進行正確的處理。
三、Leader選舉觸發時機
首先要確定的是,Leader選舉是什麼時候發起的?
- ZK集羣啓動的時刻
- 當出現Leader節點無法與其他節點通信的時刻
首先在ZK集羣啓動時還沒有Leader節點出現,此時觸發Leader選舉。
其次在集羣對外提供服務期間,如果Leader節點發生宕機或者出現網絡分區,無法與其他節點進行通信,那麼此時觸發新一輪Leader選舉。
四、Leader選舉過程
根據上面提到的選舉觸發時機,結合下圖從實例中瞭解選舉過程。
集羣初始化選舉
如上圖所示,集羣一共三個節點,在服務器初始化階段,所有節點的zxid都爲0(因爲還沒有處理過客戶端事務請求),此時節點狀態爲LOOKING狀態發生了Leader選舉階段。
- 每個節點向其他節點發送選舉投票,此時因爲是初始化選舉,所有節點都會將自己作爲Leader發送給其他節點,投票內容爲[SID,ZXID]
- 收到其他節點發來的投票後,判斷是否投票爲同一輪次選舉,也就是epoch是否相同,並且判斷是否爲LOOKING狀態的服務器,如果判斷不通過則不處理。
- 收到投票後,按照選舉規則進行投票PK,規則如下:
優先選舉ZXID最大的,如果相同選舉SID最大的。 - 如果PK結果還是自己,則跳過處理,否則將自己的選票變更爲PK後的選票再次發送給其他節點。
- 最後統計投票,當統計出超過半數節點相同的投票,則Leader節點成功選出。
- 變更自己的服務器狀態,如果是自己當選Leader變更爲LEADING,或者是FOLLOWING。
Leader宕機後選舉
也就是當出現Leader節點無法與其他節點通信的時候,此時觸發新一輪的Leader選舉,此時選舉輪次Epoch+1,然後按照初始化流程的選舉步驟一樣,發起Leader選舉。
與初始化選舉不同的是,此時由於已經對外提供了一段時間服務,所以首先將自己的服務器狀態變更爲LOOKING,然後由於各個節點的Zxid不同,按照 優先選舉ZXID最大的,如果相同選舉SID最大的 規則進行選舉。
五、新節點加入與選舉規則
新節點加入
在一個新節點加入集羣時,分爲兩種情況,一種是當前Leader已經選出,另一種是當前Leader還未選舉。
- 當Leader節點已經選出時,新加入節點爲LOOKING狀態,然後發起自身的投票選舉,這時其他收到選票的節點會返回給新節點Leader信息,此時新節點直接變更狀態,與Leader同步即可。
- 當Leader節點未選出時,按照上一小節的正常選舉流程參與選舉即可。
選舉規則
vote_sid:接收到的投票中所推舉Leader服務器的SID。
vote_zxid:接收到的投票中所推舉Leader服務器的ZXID。
self_sid:當前服務器自己的SID。
self_zxid:當前服務器自己的ZXID。
通過上面四個屬性,可以滿足如下四個規則:
- 在節點處理選票時,首先會判斷是否爲同一輪次的投票,非相同輪次不予以處理。(這裏可能由於節點宕機一段時間,這段時間發生過多次Leader選舉,宕機節點恢復時與當前輪次不同)
- 如果選舉輪次相同,優先選擇Zxid較大節點,這是因爲保證選舉出來的Leader節點具有最新最全的數據,更容易進行數據恢復同步。
- 如果Zxid相同,那麼優先選舉SID最大的節點。
- 按照上面的三個規則PK後,如果是自己,那麼不做任何變更操作。
六、選舉算法FastLeaderElection解析
之前已經介紹過QuorumCnxManager如何處理選票的通信,這裏不過多介紹。 首先明確選舉算法最重要的四個概念:
- 外部投票:接收外部其他節點選票
- 內部投票:自身的投票
- 選舉輪次:zk服務器的Leader選舉輪次
- 選票PK:通過規則來進行內部選票與外部選票的PK,決定是否變更內部選票。
FastLeaderElection算法核心流程:
整個算法流程分爲10個步驟:
- 自增選舉輪次:在每一次新輪次選舉時,都要自增選舉輪次屬性logicalclock,用來避免各個節點選舉輪次不同,導致選舉錯誤的情況。
- 初始化自身選票:在新一輪的選舉時,初始化自身的選票Vote。
- 發送初始化選票:初始化選票完成後,將選票放入到發送隊列sendQueue隊列中,由發送器WorkerSender發送出去。
- 接收外部選票:每臺服務器會不斷的從recvqueue隊列中獲取外部的投票,如果服務器發現無法取得外部投票,則立即確認是否與外部服務器失去連接,如果失去連接則立即申請創建連接。
- 判斷選舉輪次:
(1)如果外部投票選舉輪次大於內部投票,則立即更新自己的輪次,並清空收到的所有選票。
(2)外部投票小於內部投票,則直接忽略,不做處理
(3)如果輪次相同,則進入到下一步開始選票PK - 選票PK:由於之前選舉投票規則已經介紹了,這裏簡單說下:
(1)如果外部投票輪次大於內部投票,則變更內部投票
(2)輪次相同,如果外部投票ZXID大於內部投票,則變更內部投票
(3)ZXID相同,如果外部投票SID大於內部投票,則變更內部投票 - 變更投票:如果出現變更內部投票情況,將外部投票覆蓋內部投票,然後重新發送出去。
- 選票歸檔:無論是否變更投票,都將剛剛收到的外部投票放入選票集合recvset中歸檔,recvset記錄當前服務器在本輪次收到的所有外部投票結果,按照SID劃分,例如:{(1,vote1),(2,vote2)…}
- 統計投票:完成選票歸檔後,開始統計投票,判斷是否有超過半數節點認可了當前內部投票,如果有則退出選舉流程,否則返回步驟4循環繼續。
- 更新服務器狀態: 當統計投票後選出了超過半數投票的節點,那麼開始更新自身的服務器狀態,如果內部選票就是自己,則更新爲LEADING,如果不是的話,則根據情況更新爲FOLLOWING或OBSERVING。
七、總結
通過如上的介紹,已經比較清晰瞭解了ZK的選舉機制與選舉算法的實現流程,比較簡單的總結就是 誰Zxid大選誰,zxid相同誰服務器編號大選誰。
本文參考:
從Paxos到Zookeeper分佈式一致性原理與實踐。