zookeeper原理篇-Zookeeper選舉過程分析

前言

前面我們分析了zookeeper的啟動流程,中間也涉及了很多過程,其中選舉就是zookeeper的啟動核心過程之一,本篇我們來學習zookeeper的選舉流程。

選舉過程概述

經過前面的zookeeper相關的文章,我們也對zookeeper有一定的瞭解,知道在zookeeper中存在三種伺服器角色,分別是Leader,Follower以及Observer,其中Observer僅僅作為一個監控協調者的作用,並不參與zookeeper對外提供服務以及zookeeper的選舉,而zookeeper的選舉我們從前面的內容也知道,總共可以分為兩種,第一種是當整個zookeeper的叢集啟動後,進行的選舉過程,第二種則是在zookeeper執行期間,當出現Leader崩潰的過程時,zookeeper進行的選舉操作。接下來我們先從啟動的時候觸發的選舉開始學習

服務端啟動時選舉過程

我們在搭建zookeeper的時候往往會有一個配置檔案,裡面存放一個myid,用來標示叢集內不同客戶端的機器編號,當至少兩臺含有myid的機器啟動後,就開始進入了zookeeper的叢集選舉流程

1.每個server發起一個投票

由於當前是剛啟動狀態,因此每一個服務例項都會預設把自己作為Leader伺服器來發起投票,而每一個投票中包含了最基本的選舉所需要的元素,例如myid和ZXID,我們將這兩個按照選票的方式表示,例如myid為1,ZXID為0,我們將會表示為(1,0),由於每臺服務端例項優先都是按照自己是Leader發起投票,那麼server1預設生成的選票就是(1,0),server2的選票則是(2,0),以此類推

2.接受到其他服務端例項發來的選票

每臺服務端例項都會收到其他服務端例項發來的選票資訊,當收到選票後,就會開始校驗處理選票的流程

3.校驗處理選票

其他服務例項發來的選票,要經過一系列驗證,比如是不是本輪投票的選票,是否來自於Looking狀態的例項發來的選票,經過校驗後,就會和當前例項的選票資訊進行pk比較,比較的規則大致如下:

①優先比較ZXID,ZXID不一樣的時候,較大的那個選票所在的服務例項作為Leader

②如果兩個選票的ZXID相同的話,那麼就會比較myid,預設為myid較大的服務例項作為Leader

根據這個規則,我們來看看當server1收到server2的選票後,比較的流程是怎樣的,首先兩個選票都是第一輪投票選舉,所以zxid都是0,接著就要開始比較myid了,server1的myid是1,而server2的myid是2,大於自身的myid,那麼server2就應該是Leader,因此server1會更新自己的選票為(2,0),然後下次傳送的時候就是傳送新的選票資訊出去

4.統計每一次選票

每一次投票以後,都會統計所有的投票,判斷是否有過半的例項接受到了相同的選票資訊,對與當前server1和server2來說,必須這兩個例項的選票都一樣才可以算是完成了選舉流程,而如果是單數的例項的話,只需要達到(例項數 + 1) / 2的服務端例項接受到一樣的選票即可。而經過上面的流程以後,只要server1比較完選票,也發出了(2,0)的選票資訊,即可完成選舉

5.同步服務端例項狀態

一旦選舉完成,選出了Leader例項,每個服務例項都會更新自身的狀態,如果是Follower,就會變為

FOLLOWER

,如果是Leader則會變成

LEADING

狀態。

服務端執行期間進行的選舉

除了啟動zookeeper叢集的時候,一般情況下Leader會一直作為叢集中的Leader,即使叢集中的Follower掛了或者是新機器例項加入叢集中,也不會影響Leader。但是一旦Leader無法響應或者是宕機了,Zookeeper叢集將無法對外進行服務,而是進行新一輪的Leader選舉,而這個選舉的過程與初始化啟動叢集的選舉過程大體上是差不多的,但是有區別的是這個時候每個機器將要從自身的執行狀態切換到選舉狀態

1.更新自身狀態

當Leader例項掛了以後,剩下的所有Follower例項都會將自身的服務狀態變更為LOOKING,然後進行Leader選舉流程

2.一樣的選舉流程

Leader選舉的大體流程都是一樣的,這裡將不再贅述,當完成選舉以後,每個服務端例項按照自身的角色,將自身的狀態修改為對應的角色狀態,這個時候選舉完成,Zookeeper叢集恢復對外提供服務。

Zookeeper的選舉演算法

zookeeper的選舉的大概流程我們知道了,但是我們都知道,選舉的過程是基於演算法的,zookeeper的選舉演算法有哪些呢?在zookeeper中,提供了三種Leader選舉的演算法,分別是

LeaderElection

UDP版本的FastLeaderElection

以及

TCP版本的FastLeaderElection

三種選舉演算法。而選舉演算法,則是可以在zoo。cfg配置檔案中的

electionAlg

屬性來指定,

這三種選舉演算法分別對應值為0-3,其中0為LeaderElection演算法,使用的是UDP協議實現,1代表UDP版本的FastLeaderElection演算法,這種演算法是非授權模式,2代表的也是UDP版本的FastLeaderElection演算法,不過這種使用的是授權模式,3代表是TCP協議實現的FastLeaderElection演算法。

不過需要注意的是,從Zookeeper3。4。X版本開始,Zookeeper官方已經廢棄了UDP協議實現的0-2這三種Leader選舉演算法,僅僅保留了3這一種TCP協議實現的

FastLeaderElection

演算法,這也是為什麼上面我們介紹選舉的大致流程中不針對每一種選舉演算法進行分析的原因。

Leader選舉的細節

學習了選舉的大概流程以後,我們發現整體流程和演算法的設計不難,但是具體如何處理常見的問題的?這個時候我們需要深入細節來學習,首先Zookeeper為了處理不同情況,設計了多個服務端的狀態,這個狀態的定義在

org .apache .

zookeeper . server.quorum .QuorumPeer. ServerState

類中,分別如下:

LOOKING

:尋找Leader服務的狀態,處於當前狀態後,將會進行Leader選舉流程

FOLLOWING

:代表當前服務端處於跟隨者狀態,表明是Follower服務

LEADING

:代表當前服務端處於領導者狀態,表明是Leader服務

OBSERVING

:觀察者狀態,表明是Observer服務

前面我們也提到過,每次發出選票後,選票中包含了基本的元素,即ZXID和myid,而這個選票的定義在

apache.zookeeper.server.quorum.Vote

類,程式碼如下:

finalprivateint version;

finalprivatelong id;

finalprivatelong zxid;

finalprivatelong electionEpoch;

finalprivatelong peerEpoch;

我們把常見的幾個屬性進行說明,如下:

屬性說明id被選舉的SIDzxid當前Leader的事物IDelectionEpoch邏輯時鐘,解析出來的,當前處於第幾輪選舉投票,每次進入新一輪投票後,都會加1peerEpoch當前被選舉的Leader的epochstate當前服務所處於的狀態

學習了這些後,我們來看看選舉的通訊,前面我們有聊過

CilentCnxn

是Zookeeper客戶端中用於處理I/O網路通訊的管理器,而對應的Zookeeper的server中也有一個類——

QuorumCnxManager

類來接受和處理Leader選舉中的通訊,而整個過程可以劃分幾個部分,大致如下:

訊息佇列處理訊息

QuorumCnxManager

類中維護了很多佇列,用於儲存接收到的、等待發送的訊息,還定義了訊息傳送器等,除了接受佇列以外,其他的佇列都是按照SID分組的集合。其中常見的佇列和屬性定義如下:

recvQueue

:訊息接受佇列,用於存放接受來的所有的訊息

queueSendMap

:訊息待發送佇列,用於儲存等待發送的訊息集合,定義為一個Map,按照SID分組設定為key,並且每一個SID對應的都維護了一個佇列,保證收發訊息互不影響

senderWorkMap

:傳送器集合,每一個senderWork傳送器都對應一個遠端連線的zookeeper,負責傳送訊息,在senderWorkMap內部,也是按照SID分組進行維護的。

lasteMessageSent

:最近傳送的訊息,在這個集合中,會為每一個SID維護一個最新發送的訊息

建立連線

為了能彼此之間通訊,zookeeper叢集中的例項需要兩兩建立連線,

QuorumCnxManager

類在啟動的時候會建立一個

ServerSokect

來監聽Leader選舉的通訊埠,在接受到請求的時候,會呼叫

receiveConnection

函式來處理,但是為了避免重複的建立TCP連線,Zookeeper建立了一個規則,只允許SID大的機器往SID小的機器建立連線,當連線連理後,根據遠端服務例項的SID建立對應的

senderWorker

和對應的訊息接收器

RecvWorker

訊息接受和傳送

當訊息接收器不停的收到訊息後,會將其儲存在

recvQueue

佇列中,訊息傳送比較簡單,由於每一個SID都有一個維護的獨立的

SendWorker

,只需要不停的從

queueSendMap

獲取要傳送的資料進行傳送即可,傳送完畢後,會將剛剛傳送的訊息存入

lasteMessageSent

中,但是需要注意的是,當發現代傳送訊息的佇列是空的時候,就會從

lasteMessageSent

中獲取剛剛傳送的訊息,然後再次作為訊息傳送出去,這麼設計的原因是為了防止接受方沒有收到訊息,或者是收到訊息後掛了,導致訊息沒處理完,因為Zookeeper自身對重複訊息有處理機制,因此重複傳送訊息,可以保證能正確處理訊息

FastLeaderElection演算法

zookeeper的選舉網路IO模組我們大致知道了,接下來我們來看看

FastLeaderElection

選舉演算法的核心演算法實現,流程圖如下:

zookeeper原理篇-Zookeeper選舉過程分析

1.自增選舉次數

FastLeaderElection

的實現中,有一個

logicalclock

屬性,用於標識當前選舉的次數,zookeeper要求每次發起選舉的時候必須是在同一次選舉週期中,因此在每一次選舉之前,都會觸發

logicalclock

的自增,達到當前的選舉週期

2.初始化選票

前面我們已經知道了選票類的定義在

apache.zookeeper.server.quorum.Vote

,初始化階段的時候,每臺伺服器都會推舉自己為Leader,因此都會先初始化一個以自己為主的選票

3.將初始化的選票傳送

初始化完選票以後,會將自己的選票資訊存入

sendQueue

佇列中,然後用對應每一個

SID

workerSender

負責傳送出去

4.接受外部投票資訊

初始化階段,除了傳送自身的選票資訊以外,還會接受來自其他的服務例項發來的選票資訊,這些資訊存入

recvQueue

佇列中,如果發現無法獲取到其他選票資訊,就會確認當前服務例項是否和其他的服務例項保持著連線,如果發現連線斷開或者是沒有連線,則會再次建立連線,當然這裡建立連線依然

是對比當前服務SID的服務發起連線,防止出現重複建立連線

5.判斷選舉次數

當傳送完初始化選票後,就會開始處理接受來的其他服務例項的選票資訊,首先判斷接受到的外部投票的選舉次數是否大於當前的選票

如果大於當前服務的選票中的選舉次數,那麼則會更新當前服務的logicalclock,並且清空所有收到的選票,再次拿選票和外部投票進行選票的比較,確定是否真的要更改自身的選票,然後重新發送選票資訊。

如果外部選票的選舉次數小於當前服務例項的選舉次數,那麼直接無視掉這個選票資訊,並且繼續傳送自身的選票出去

如果外部選票和自身服務例項的選舉次數一致,那麼就需要進入選票之間的比較操作

6.選票的比較

選票比較就是整個選舉演算法的核心邏輯,實現在

FastLeaderElection#totalOrderPredicate

方法,選票比較主要是為了確定當前服務例項的選票資訊是否需要更改以後再次傳送新的選票資訊,因此會按照選舉次數、ZXID和SID來比較:

如果外部投票的選舉次數大於當前服務例項的選舉次數,就需要投票變更

如果選舉輪次一致,那麼比較ZXID,如果外部選票的ZXID比較大,需要改變自身服務例項的選票資訊

如果ZXID一致,那麼就需要比較SID,外部投票的SID較大,自身服務例項的選票資訊需要改變

7.更改選票資訊,再次傳送

經過選票比較以後,如果發現需要更改自身的選票資訊,會先修改自身的選票資訊,然後再次按照新的選票資訊傳送出去

8.選票統計歸檔

每個服務例項在傳送和接受過程中,無論是否清空還是無視部分選票,都會把每一個選票的資訊存入recvSet中進行歸檔統計,內部儲存按照SID區分選票資訊,然後進行統計計算,只要發現超過半數以上的服務例項進行了投票,就可以停止傳送選票資訊,結束選舉,否則繼續重複上面的傳送和接受選票過程

9.選舉Leader以後,進行服務例項的狀態變更

當停止了投票以後,統計出來最終的Leader服務例項以後,就開始修改每個服務例項的狀態,具體的邏輯是檢查選出來的Leader是不是自己,如果是,則將自身的狀態修改為

LEADING

,如果不是,則根據情況修改自身的狀態為

FOLLOWING

或者是

OBSERVING

至此,FastLeaderElection演算法的核心流程完成,但是我們需要注意的是,

前面4-8的步驟,可能會經過很多次,因為每一次選舉過程中,即使收到了過半的選票,不一定就能選出Leader服務,可能需要選舉多次,並且每一次選舉投票過程中,會有多次傳送選票的過程,因為每一個服務例項傳送選票的週期是隨機的,同時還需要注意的一點是,即使選票超過半數了,選出Leader服務例項了,也不是立刻結束,而是等待200ms,確保沒有丟失其他服務的更優的選票