天天看點

分布式一緻性算法之Paxos原理剖析概述選舉發生時機一個節點成為leader條件要解決的問題嘗試解決方案選舉算法流程算法中涉及的重要變量運作執行個體

zookeeper叢集中,隻有一個節點是leader節點,其它節點都是follower節點(實際上還有observer節點,不參與選舉投票,在這裡我們先忽略,下同)。所有更新操作,必須經過leader節點,leader節點和follower節點之間保持着資料同步和心跳。

分布式一緻性算法之Paxos原理剖析概述選舉發生時機一個節點成為leader條件要解決的問題嘗試解決方案選舉算法流程算法中涉及的重要變量運作執行個體

用戶端使用zookeeper時,可能會連到follower身份的server上,也可能會連到leader身份的server上。

三類角色分工如下:

leader:處理寫請求,單點

follower:處理用戶端請求,參與投票

observer:不參與leader選舉投票,隻處理用戶端請求

在一個zookeeper叢集裡,有多少個server是固定的,每個節點有一個唯一id,辨別它自己,另外,每個server還有用于選舉的ip和port,這些都在配置檔案中。一個具體的例子如下:

server.1= server1的ip位址:2888:3888

server.2= server2的ip位址:2888:3888

server.2= server3的ip位址:2888:3888

這裡有3個server,其id分别為1、2、3。2888為節點和leader交換資訊的端口,3888為選舉端口。這個節點的id,在投票時,使用者辨別參加競選的節點的身份。

<b>問題:這個leader節點是怎麼确定的?</b>

答案:zookeeper系統自己選舉出來的,所有server節點(observer除外),都參與這個選舉。這樣做的好處是:當現在leader挂掉了之後,系統可以重新選舉一個節點做leader。

zookeeper的選舉算法能保證:隻要超過半數節點還活着,就一定能選舉出唯一個一個節點作為leader。

   當任何一個節點進入looking狀态時,選舉開始,進入looking狀态有如下原因:

   1、節點剛啟動,使自己進入選舉狀态

   2、發現leader節點挂掉了

   zookeeper中的leader怎麼知道follower還活着?follower怎麼知道leader還活着?leader會定時向follower發ping消息;follower會定時向leader發ping消息。當發現無法ping通leader時,就會将自己的狀态改為looking,并發起新一輪選舉。處于選舉模式時,zookeeper服務不可用。

    一個節點要成為leader,必須得到至少n/2+1(即半數以上節點)投票,實際上,在實作時,還可以考慮其它規則,比如節點權重。

    為什麼要保證至少n/2+1的節點同意?因為這樣能保證本節點得到多數派的支援。因為每一個節點,隻能支援一個節點成為leader,是以,隻要一個節點獲得至少n/2+1選票,就一定會比其它任何節點得到的選票多。

    這個規則意味着,如果超過半數以上的節點挂掉,zookeeper是選舉不出leader節點的,是以,zookeeper叢集最多允許n/2節點故障。

    選舉算法目标是確定一定要選出一個唯一的leader節點。這有兩層含義:

    1、一定要選出一個節點作為leader

    2、這個leader一定要唯一

    為此,要解決如下問題:

    1、在一次選舉中,節點應該把票投給誰?

    規則:每個節點有一個唯一id,在選舉中,節點總是把票投給id最大的那個節點,這樣,id大的節點更有可能成為leader,天生就是做上司的料。

    2、在一次選舉過程中,有些節點由于沒有啟動而沒參加(有些人去國外了,沒有趕上這次大選,當他回國後,進入looking狀态,要發起選舉,怎麼辦?),後來這個節點啟動了,此時要求選舉,怎麼解決?

    3、運作過程中,leader節點挂掉了,怎麼辦?

    此時其它節點會發現leader挂了,會發起新一輪選舉,最後選出新leader。

    1、直接指定一個節點做leader,例如,永遠都讓id最大節點當leader,這個想法最簡單。問題:這個節點挂了怎麼辦?這會出現單點問題。

    2、每次選舉中,讓活着節點中,id最大節點當leader。問題:1、其它節點怎麼知道活着節點中,誰id最大?

    選舉開始時,每個節點為自己生成一張投票,推薦自己成為leader,并把投票發送給其它節點,這相當于paxos算法中的proposer角色。接下來,節點啟動一個接收線程接收其它節點發送過來的投票,并對選票進行處理,這相當于paxos中的acceptor角色。簡單說,節點之間通過這種消息發送(投票),最終選舉出leader。

    當收到其他它節點的選票之後,會和自己的投票比較,如果比自己的投票好(比如推薦的leader的id更大,選舉輪數更新),則更新自己的選票,接下來把收到的選票放在選票清單裡(該清單存儲了所有節點的投票,是一個key-value結構,key為節點的id,value為該節點的投票)。并再次把自己的投票發送給其它節點。

    接下來節點會統計選票清單中每個節點獲得的票數,如果有一個節點獲得超過半數的選票,則認為該節點是leader。如果本節點就是,則将自身的狀态置為leading,表明自己是leader;否則将自己的狀态置為following,表明自己是follower。

    通過若幹輪的消息交換,最終,會有一個節點獲得超過一半的選票而成為leader。這種方法的精髓在于,每個節點在不需要獲得所有節點的資訊(投票結果)的前提下,達成一緻意見,選出leader。

volatile long

logicalclock;

    表示選舉輪數,在lookforleader開始的時候會加1,,另外,在收到其他節點的投票資訊時,如果其它節點的electionepoch比本值大,本值會被賦成electionepoch。也就是說,每次節點啟動時,該值為0?這個值隻在節點存活的時候有意義?即節點重新開機後,該值為0。

    long proposedleader;

    該值為本節點推薦的leader的id,初始時為自己,後面會更新,這個值不會從檔案中讀,也就是說,重新開機後會自動使用本節點id。getinitid源代碼如下:

    public

long getid() {

       return myid;

    }

    long proposedzxid;

    本節點建議的zxid,在starter函數中,被初始化為-1;在updateproposal函數中,會更新該變量的值。

    updateproposal(getinitid(),

getinitlastloggedzxid(), getpeerepoch());

        private

long getinitlastloggedzxid(){

if(self.getlearnertype() == learnertype.participant)

return self.getlastloggedzxid();

else return long.min_value;

 }

 public long

getlastloggedzxid() {

     if (!zkdb.isinitialized()) {

           loaddatabase();

     }

     return

zkdb.getdatatreelastprocessedzxid();

    long proposedepoch;

    表示本節點推薦的選舉輪數,在updateproposal函數更新選票時,會更新該值。節點啟動初始化時候,第一次調用updateproposal,會把proposedepoch的值賦為getpeerepoch,而該函數又會調用getcurrentepoch,getcurrentepoch的代碼如下:

long getcurrentepoch() throws ioexception {

             if

(currentepoch == -1) {

                  currentepoch

= readlongfromfile(current_epoch_filename);

             }

             return

currentepoch;

    }

    這表明,該值會從日志檔案中讀出來。也就是說,節點重新開機後,會使用上次活着的時候的值。

     為什麼有了zxid還需要epoch?zxid是用來表示資料的新舊,而epoch是用來表示選舉的輪數。

    假設zookeeper叢集中有3個節點,其id分别為1、2、3。整個叢集開始運作時,每個節點zxid都為1。

節點1、2、3啟動後,都進入looking狀态,開始leader選舉。每個節點的proposedleader即推薦的leader都是自己;logicalclock值都為1;建議的proposedzxid值都為1;建議的proposedepoch值都為1;投票清單為每個節點投自己的一票(1-&gt;1,2-&gt;2,3-&gt;3)。節點1首先向2、3發送自己的投票消息:

分布式一緻性算法之Paxos原理剖析概述選舉發生時機一個節點成為leader條件要解決的問題嘗試解決方案選舉算法流程算法中涉及的重要變量運作執行個體

節點2、3收到節點1的投票消息,首先檢視1的狀态,發現1處于looking狀态。接下來,判斷1發來的electionepoch和本地邏輯時鐘logicalclock的大小,發現兩者相等(都為1)。接着判斷leader、zxid、peerepoch和本地proposedleader、proposedzxid、proposedepoch的大小,節點2發現節點1推薦的leader的id比自己小(1&lt;2),節點3也發現節點1推薦的leader的id比自己的小(1&lt;3),是以不用更新自己的投票。接下來,節點2、3把節點1的投票放入自己的投票清單中,這樣,節點2收到的投票的清單為:

         1-&gt;1

         2-&gt;2

         節點3的為:

         3-&gt;3

     節點2、3再判斷此次投票是否可以結束,發現不能結束。如下圖所示:

分布式一緻性算法之Paxos原理剖析概述選舉發生時機一個節點成為leader條件要解決的問題嘗試解決方案選舉算法流程算法中涉及的重要變量運作執行個體

節點2向節點1、3發送自己的投票資訊,節點3由于發送線程的故障原因,投票資訊一直沒有出去:

分布式一緻性算法之Paxos原理剖析概述選舉發生時機一個節點成為leader條件要解決的問題嘗試解決方案選舉算法流程算法中涉及的重要變量運作執行個體

      在2發出的投票資訊中,選擇的leader是它自己。

節點1、3收到節點2的投票消息。節點1比較自己的logcalclock和節點2發來的electionepoch的大小,二者相等,接下來比較leader、zxid、peerepoch和本地proposedleader、proposedzxid、proposedepoch的大小,發現節點2推薦的leader的id(2)比自己的proposedleader(1)大,于是更新自己的選票,将proposedleader改為2。然後,節點1将2的選票(2-&gt;2)放入自己收到的投票箱中,接着判斷投票是否可以結束(調用函數termpredicate),由于節點2被超過半數的節點選擇(1、2),是以選舉可以結束,由于自己不是leader,節點1将自己的狀态改為following。

節點3比較自己的logcalclock和節點2發來的electionepoch的大小,二者相等,接下來比較leader、zxid、peerepoch和本地proposedleader、proposedzxid、proposedepoch的大小,發現節點2推薦的leader的id(2)比自己的proposedleader(3)小,不用更新自己的選票。然後,節點3将2的選票(2-&gt;2)放入自己收到的投票箱中,接着判斷投票是否可以結束(調用函數termpredicate),由于沒有節點獲得超過半數的選票,是以選舉繼續。

分布式一緻性算法之Paxos原理剖析概述選舉發生時機一個節點成為leader條件要解決的問題嘗試解決方案選舉算法流程算法中涉及的重要變量運作執行個體

節點1收到節點2的選票,更新選票後,再向節點1、3發送自己的投票資訊:此時,節點1選的leader已經變為2,而且節點1的狀态已經變成following。

分布式一緻性算法之Paxos原理剖析概述選舉發生時機一個節點成為leader條件要解決的問題嘗試解決方案選舉算法流程算法中涉及的重要變量運作執行個體

節點2在收到節點1的選票資訊後,判斷節點1的狀态,發現為following,這表明,節點1已經認為leader選出來了,并且是2。節點2首先更新自己的收票箱,将1的投票改為2,接着,判斷選舉是否結束,發現确實可以結束,節點2就更新自己的狀态,由于發現自己是被半數以上人推薦的leader,是以把自己的狀态改為leading。同樣,節點3在收到節點1的投票資訊後,判斷節點1的狀态,發現為following,這表明,節點1已經認為leader選出來了,并且是2。節點3首先更新自己的收票箱,将1的投票改為2,接着,判斷選舉是否結束,發現确實可以結束,節點3就更新自己的狀态,由于發現自己不是被半數以上人推薦的leader,是以把自己的狀态改為following。至此,選舉結束,選出來的leader為2,1、3都為follower。

分布式一緻性算法之Paxos原理剖析概述選舉發生時機一個節點成為leader條件要解決的問題嘗試解決方案選舉算法流程算法中涉及的重要變量運作執行個體

繼續閱讀