1. 程式人生 > >zookeeper選舉原理講解

zookeeper選舉原理講解

閱讀時參考的版本是3.3.3.

簡單的說一下zookeeper工作的過程,如果對這個過程還不太清楚,或者說對它如何使用等不太清楚的,可以參考一下其他的文章,比如這篇,這一系列的文章將不講解它如何使用(實際上我也沒有在具體專案中使用過,只是簡單的配置執行起來大概曉得如何工作而已).

zookeeper有兩種工作的模式,一種是單機方式,另一種是叢集方式.單機方式不屬於這裡分析的範疇,因為研究zookeeper的目的就在於研究一個zookeeper叢集的機器如何協調起來工作的.

要配置幾臺zookeeper一起工作,大家在開始必須使用相同的配置檔案,配置檔案中有一些配置項,但是與叢集相關的是這一項:

server.1=192.168.211.1:2888:3888
server.2=192.168.211.2:2888:3888


這裡定義了兩臺伺服器的配置,格式為:

server.serverid=serverhost:leader_listent_port:quorum_port

顧名思義,serverid是本伺服器的id,leader_listen_port是該伺服器一旦成為leader之後需要監聽的埠,用於接收來自follower的請求,quorum_port是叢集中的每一個伺服器在最開始選舉leader時監聽的埠,用於伺服器互相之間通訊選舉leader.

需要注意的是,server id並沒有寫在這個配置檔案中,而是在datadir中的myid檔案中指定,我理解這麼做的目的是:所有的伺服器統一使用一個配置檔案,該配置檔案裡面沒有任何與特定伺服器相關的資訊,這樣便於釋出服務的時候不會出錯,而獨立出來一個檔案專門存放這個server id值.

zookeeper叢集工作的過程包括如下幾步:
1) recovery,這個過程泛指叢集伺服器的啟動和恢復,因為恢復也可以理解為另一種層面上的”啟動”–需要恢復歷史資料的啟動,後面會詳細講解.
2) broadcast,這是啟動完畢之後,叢集中的伺服器開始接收客戶端的連線一起工作的過程,如果客戶端有修改資料的改動,那麼一定會由leader廣播給follower,所以稱為”broadcast”.

展開來說,zookeeper叢集大概是這樣工作的:
1) 首先每個伺服器讀取配置檔案和資料檔案,根據serverid知道本機對應的配置(就是前面那些地址和埠),並且將歷史資料載入進記憶體中.
2) 叢集中的伺服器開始根據前面給出的quorum port監聽叢集中其他伺服器的請求,並且把自己選舉的leader也通知其他伺服器,來來往往幾回,選舉出叢集的一個leader.
3) 選舉完leader其實還不算是真正意義上的”leader”,因為到了這裡leader還需要與叢集中的其他伺服器同步資料,如果這一步出錯,將返回2)中重新選舉leader.在leader選舉完畢之後,叢集中的其他伺服器稱為”follower”,也就是都要聽從leader的指令.
4) 到了這裡,叢集中的所有伺服器,不論是leader還是follower,大家的資料都是一致的了,可以開始接收客戶端的連線了.如果是讀型別的請求,那麼直接返回就是了,因為並不改變資料;否則,都要向leader彙報,如何通知leader呢?就是通過前面講到的leader_listen_port.leader收到這個修改資料的請求之後,將會廣播給叢集中其他follower,當超過一半數量的follower有了回覆,那麼就相當於這個修改操作哦了,這時leader可以告訴之前的那臺伺服器可以給客戶端一個迴應了.
可以看到,上面1),2),3)對應的recovery過程,而4)對應的broadcast過程.

這裡只是簡單的描述了一下zookeeper叢集的工作原理,後面將分別展開來討論.

2、Fast Leader選舉演算法(領導者選舉)

如何在zookeeper叢集中選舉出一個leader,zookeeper使用了三種演算法,具體使用哪種演算法,在配置檔案中是可以配置的,對應的配置項是”electionAlg”,其中1對應的是LeaderElection演算法,2對應的是AuthFastLeaderElection演算法,3對應的是FastLeaderElection演算法.預設使用FastLeaderElection演算法.其他兩種演算法我沒有研究過,就不多說了.

要理解這個演算法,最好需要一些paxos演算法的理論基礎.

1) 資料恢復階段
首先,每個在zookeeper伺服器先讀取當前儲存在磁碟的資料,zookeeper中的每份資料,都有一個對應的id值,這個值是依次遞增的,換言之,越新的資料,對應的ID值就越大.

2) 向其他節點發送投票值
在讀取資料完畢之後,每個zookeeper伺服器傳送自己選舉的leader(首次選自己),這個協議中包含了以下幾部分的資料:
    a)所選舉leader的id(就是配置檔案中寫好的每個伺服器的id) ,在初始階段,每臺伺服器的這個值都是自己伺服器的id,也就是它們都選舉自己為leader.
    b) 伺服器最大資料的id,這個值大的伺服器,說明存放了更新的資料.
    c)邏輯時鐘的值,這個值從0開始遞增,每次選舉對應一個值,也就是說:  如果在同一次選舉中,那麼這個值應該是一致的 ;  邏輯時鐘值越大,說明這一次選舉leader的程序更新.
    d) 本機在當前選舉過程中的狀態,有以下幾種:LOOKING,FOLLOWING,OBSERVING,LEADING,顧名思義不必解釋了吧.

3)接受來自其他節點的資料

每臺伺服器將自己伺服器的以上資料傳送到叢集中的其他伺服器之後,同樣的也需要接收來自其他伺服器的資料,它將做以下的處理:
(1)如果所接收資料中伺服器的狀態還是在選舉階段(LOOKING 狀態),那麼首先判斷邏輯時鐘值,又分為以下三種情況:
a) 如果傳送過來的邏輯時鐘大於目前的邏輯時鐘,那麼說明這是更新的一次選舉,此時需要更新一下本機的邏輯時鐘值,同時將之前收集到的來自其他伺服器的選舉清空,因為這些資料已經不再有效了.然後判斷是否需要更新當前自己的選舉情況.在這裡是根據選舉leader id,儲存的最大資料id來進行判斷的,這兩種資料之間對這個選舉結果的影響的權重關係是:首先看資料id,資料id大者勝出;其次再判斷leader id,leader id大者勝出.然後再將自身最新的選舉結果(也就是上面提到的三種資料)廣播給其他伺服器).

    b) 傳送過來資料的邏輯時鐘小於本機的邏輯時鐘,說明對方在一個相對較早的選舉程序中,這裡只需要將本機的資料傳送過去就是了

    c) 兩邊的邏輯時鐘相同,此時也只是呼叫totalOrderPredicate函式判斷是否需要更新本機的資料,如果更新了再將自己最新的選舉結果廣播出去就是了.

然後再處理兩種情況:
1)伺服器判斷是不是已經收集到了所有伺服器的選舉狀態,如果是,那麼這臺伺服器選舉的leader就定下來了,然後根據選舉結果設定自己的角色(FOLLOWING還是LEADER),然後退出選舉過程就是了.
2)即使沒有收集到所有伺服器的選舉狀態,也可以根據該節點上選擇的最新的leader是不是得到了超過半數以上伺服器的支援,如果是,那麼當前執行緒將被阻塞等待一段時間(這個時間在finalizeWait定義)看看是不是還會收到當前leader的資料更優的leader,如果經過一段時間還沒有這個新的leader提出來,那麼這臺伺服器最終的leader就確定了,否則進行下一次選舉.

(2) 如果所接收伺服器不在選舉狀態,也就是在FOLLOWING或者LEADING狀態

做以下兩個判斷:
    a) 如果邏輯時鐘相同,將該資料儲存到recvset,如果所接收伺服器宣稱自己是leader,那麼將判斷是不是有半數以上的伺服器選舉它,如果是則設定選舉狀態退出選舉過程
    b) 否則這是一條與當前邏輯時鐘不符合的訊息,那麼說明在另一個選舉過程中已經有了選舉結果,於是將該選舉結果加入到outofelection集合中,再根據outofelection來判斷是否可以結束選舉,如果可以也是儲存邏輯時鐘,設定選舉狀態,退出選舉過程.
程式碼如下:

以一個簡單的例子來說明整個選舉的過程.
假設有五臺伺服器組成的zookeeper叢集,它們的id從1-5,同時它們都是最新啟動的,也就是沒有歷史資料,在存放資料量這一點上,都是一樣的.假設這些伺服器依序啟動,來看看會發生什麼.
1) 伺服器1啟動,此時只有它一臺伺服器啟動了,它發出去的報沒有任何響應,所以它的選舉狀態一直是LOOKING狀態
2) 伺服器2啟動,它與最開始啟動的伺服器1進行通訊,互相交換自己的選舉結果,由於兩者都沒有歷史資料,所以id值較大的伺服器2勝出,但是由於沒有達到超過半數以上的伺服器都同意選舉它(這個例子中的半數以上是3),所以伺服器1,2還是繼續保持LOOKING狀態.
3) 伺服器3啟動,根據前面的理論分析,伺服器3成為伺服器1,2,3中的老大,而與上面不同的是,此時有三臺伺服器選舉了它,所以它成為了這次選舉的leader.
4) 伺服器4啟動,根據前面的分析,理論上伺服器4應該是伺服器1,2,3,4中最大的,但是由於前面已經有半數以上的伺服器選舉了伺服器3,所以它只能接收當小弟的命了.
5) 伺服器5啟動,同4一樣,當小弟.

以上就是fastleader演算法的簡要分析,還有一些異常情況的處理,比如某臺伺服器宕機之後的處理,當leader宕機之後的處理等等,後面再談.

3、Leader與Follower同步資料(原子廣播)

      根據 Fast Leader選舉演算法中的分析,如果一臺zookeeper伺服器成為叢集中的leader,那麼一定是當前所有伺服器中儲存資料最多(不是最新??)的伺服器,所以在這臺伺服器成為leader之後,首先要做的事情就是與叢集中的其它伺服器(現在是follower)同步資料,保證大家的資料一致,這個過程完畢了才開始正式處理來自客戶端的連線請求.

      Fast Leader選舉演算法中提到的同步資料時使用的邏輯時鐘,它的初始值是0,每次選舉過程都會遞增的,在leader正式上任之後做的第一件事情,就是根據當前儲存的資料id值,設定最新的邏輯時鐘值。

    隨後,leader構建NEWLEADER封包,該封包的資料是當前最大資料的id,廣播給所有的follower,也就是告知follower leader儲存的資料id是多少,大家看看是不是需要同步。然後,leader根據follower數量給每個follower建立一個執行緒LearnerHandler,專門負責接收它們的同步資料請求.leader主執行緒開始阻塞在這裡,等待其他follower的迴應(也就是LearnerHandler執行緒的處理結果),同樣的,只有在超過半數的follower已經同步資料完畢,這個過程才能結束,leader才能正式成為leader.

leader所做的工作:

所以其實leader與follower同步資料的大部分操作都在LearnerHandler執行緒中處理的,接著看這一塊.
leader接收到的來自某個follower封包一定是FOLLOWERINFO,該封包告知了該伺服器儲存的資料id.之後根據這個資料id與本機儲存的資料進行比較:
1) 如果資料完全一致,則傳送DIFF封包告知follower當前資料就是最新的了.
2) 判斷這一階段之內有沒有已經被提交的提議值,如果有,那麼:
    a) 如果有部分資料沒有同步,那麼會發送DIFF封包將有差異的資料同步過去.同時將follower沒有的資料逐個傳送COMMIT封包給follower要求記錄下來.
    b) 如果follower資料id更大,那麼會發送TRUNC封包告知截除多餘資料.(一臺leader資料沒同步就宕掉了,選舉之後恢復了,資料比現在leader更新)
3) 如果這一階段內沒有提交的提議值,直接傳送SNAP封包將快照同步傳送給follower.
4)訊息完畢之後,傳送UPTODATE封包告知follower當前資料就是最新的了,再次傳送NEWLEADER封包宣稱自己是leader,等待follower的響應.

follower做的工作:
(1)會嘗試與leader建立連線,這裡有一個機制,如果一定時間內沒有連線上,就報錯退出,重新回到選舉狀態.
(2)其次在傳送FOLLOWERINFO封包,該封包中帶上自己的最大資料id,也就是會告知leader本機儲存的最大資料id.
(3)根據前面對LeaderHandler的分析,leader會根據不同的情況傳送DIFF,UPTODATE,TRUNC,SNAP,依次進行處理就是了,此時follower跟leader的資料也就同步上了.
(4)由於leader端傳送的最後一個封包是UPTODATE,因此在接收到這個封包之後follower結束同步資料過程,傳送ACK封包回覆leader.

以上過程中,任何情況出現的錯誤,伺服器將自動將選舉狀態切換到LOOKING狀態,重新開始進行選舉.