1. 程式人生 > >ZooKeeper系列之(十):投票選舉(2)

ZooKeeper系列之(十):投票選舉(2)

ZooKeeper的選舉過程預設使用FastLeaderElection類,FastLeaderElection啟動時啟動Messenger收發選舉資訊。選舉完成後選出1個Leader和若干Follower。

首先理解幾個概念:   

Epoch:投票週期,用於區分每一個round,每一次建立一個新的leader-follower關係,都會有一個唯一的epoch值去標識。就好像皇帝登基必須得有一個年號,與之前或之後的皇帝進行區分。剛啟動是從檔案讀取儲存的currentEpoch和acceptedEpoch值。預設是-1。

zxid:事務ID,表示Zookeeper當前寫操作的序列號,確保寫操作的按順序執行。有一種場景是當Follower的最大zxid大於Leader的zxid時,Leader會發送TRUNC包給Follower截斷多餘的事務,保證和Leader資料一致。zxid會在ZxDataBase的loadDataBase方法中初始化。每次執行一次寫事務zxid就會加1,預設為0。

選舉規則:先判斷雙方的Epoch,留取大的Epoch;再看雙方的zxid,留取大的zxid一方。最後的結果就是(Epoch<32|zxid)的一方會被推舉為Leader。

叢集間選舉使用Messenger來負責選舉資訊的互動,Messenger底層使用QuorumCnxManager管理本機和叢集其他機器之間的Socket資料傳遞,通訊流程示意圖如下所示。

叢集中所有LOOKING狀態的機器同時啟動選舉過程。選舉結束則產生一個Leader和多個Follower。然後就是各Follower連線到Leader進行事務初始化,實現資料同步。

Observer不參與選舉,但會接收Leader的資訊同步要求。

QuorumPeer剛啟動時會設定初始狀態為LOOKING,然後啟動FastLeaderElection選舉過程,首先將自己作為Vote群發給其他QuorumPeer,群發的資料結構為Notification。同時啟動Messenage的收發執行緒,呼叫lookForLeader方法發起一輪選舉Leader的過程。

在Leader/Follower狀態,如果接收到遠方LOOKING節點尋找Leader而傳送的Notification包,則Messenger會自動回覆Leader資訊給對方;在LOOKING狀態,則Messenger會回覆當前Vote節點(臨時Leader)資訊給對方。

預設選舉規則規定當超過半數的QuorumPeer都認同同一個Leader時,選舉過程結束,各QuorumPeer將自己設定為Leader、Follower或Observer。當然有興趣的讀者也可以嘗試建立自己的選舉規則類,只要實現QuorumVerifier即可。

進行Fast leader election的先決條件:

1、 每個QuorumPeer都知道其他QuorumPeer的ip地址,並知道QuorumPeer的總數。

2、 每個QuorumPeer一開始都是發起一個vote,選取自己為leader。向其他所有的QuorumPeer傳送vote的notification,並等待回覆。

3、 根據QuorumPeer的狀態處理vote notification訊息。

選舉過程中產生的臨時Leader成為Vote,當選舉結束後最後一輪產生的Vote即成為新的Leader。

QuorumPeer每次發起選舉都呼叫lookForLeader方法實現,首先將自己設定為LOOKING狀態。該方法是FastLeaderElection類的主方法,具體的流程如下:

  • 首先更新選舉週期logicalclock, 並把自己作為leader作為投票發給所有其他的server。
  • 然後進入本輪投票的迴圈,直到自己不再是LOOKING狀態。
  1. 從recvqueue獲取一個網路包(recvqueue的資料來自Messenger),如果沒有收到包則檢查是否要重連和重發自己的投票。
  2. 收到投票後判斷對方投票的狀態。
  1. LOOKING:
    • 如果對方投票的週期(Epoch)大於自己的週期(Epoch),那就清空自己的已經收到的投票集合recvset,並將自己作為候選和對方投票的leader做比較,選出大的作為新的投票,然後再發送給所有人。 這裡比較大小是通過比較(zxid,sid)這個二元組來的,zxid大的就大,否則sid大的就大。
    • 如果對方的投票週期小於自己,則忽略對方的投票。
    • 如果週期相等,則比較對方的投票和自己認為的候選,選出大的作為新的候選,然後再發送給所有人。
    • 然後判斷當前收到的投票是否可以得出誰是leader的結論,這裡主要是通過判斷當前的候選leader在收到的投票中是否佔了多數。
    • 如果候選leader在收到的投票中佔了多數,則再等待finalizeWait時鐘,看是否有人修改leader的候選,如果修改了則把投票放到recvqueue中再重新迴圈。
  2. OBSERVING:如果對方是一個觀察者,由於它沒有投票權,則無視它
  3. FOLLOWING或LEADING:
  • 如果對方和自己再一個時鐘週期,說明對方已經完成選舉,如果對方說它是leader,那我們就把它作為leader,否則就要比較下對方選舉的leader在自己這裡是否佔有多數,並且選舉的leader確認了願意當leader,如果都通過了,就把這個候選作為自己的leader
  • 如果對方和自己不在一個時鐘週期,說明自己掛掉後又恢復起來,這個時候把別人的投票收集到一個單獨的集合outofelection(從名字可以看出這個集合不是用在選舉判斷),如果對方的投票在outofelection中佔有大多數,並且leader也確認了自己願意做leader,這個時候更新自己的選舉週期logicalclock,並修改自己的狀態為FOLLOWING或LEADING

 

lookForLeader程式碼較長,我們先看看它的主體結構。

public Vote lookForLeader() throws InterruptedException {
     try {
        HashMap<Long, Vote> recvset = new HashMap<Long, Vote>();
        HashMap<Long, Vote> outofelection = new HashMap<Long, Vote>();
       int notTimeout = finalizeWait;
        synchronized(this){
             logicalclock.incrementAndGet();
             updateProposal(getInitId(), getInitLastLoggedZxid(), 
getPeerEpoch());
       }
        sendNotifications();
        while ((self.getPeerState() == ServerState.LOOKING) && (!stop)){           
            Notification n = recvqueue.poll(notTimeout,
                        TimeUnit.MILLISECONDS);
            if (self.getCurrentAndNextConfigVoters().contains(n.sid)) {
               switch (n.state) {
                  case LOOKING:
                       {A程式碼}
                        break;
                    case OBSERVING:
                        LOG.debug("Notification from observer: " + n.sid);
                        break;
                    case FOLLOWING:
                    case LEADING:                    
                        {B程式碼}
                        break;
                    default:
                        LOG.warn("Notification state unrecoginized: " + n.state
                              + " (n.state), " + n.sid + " (n.sid)");
                        break;
                    }
                } 
            }
            return null;
        }   
 }

具體分析如下:

首先通過sendNotification方法告訴叢集我在尋找Leader,叢集中其他機器會在Messenger程序中接收到Notification,然後都會回覆誰應該是當前Leader。

FastLeaderElection收集到足夠的Notification訊息,來判斷到底誰才是合法的Leader。為此它對每條Notification訊息進行下列判斷:

A. 回覆Notification的傳送方也是LOOKING狀態

如果回覆Notification的傳送方也是LOOKING狀態,說明它還不知道最終的Leader是誰,這時候FastLeaderElection和傳送方比較,看看誰的潛在Leader的Epoch和zxid最大,最大的設定為當前的候選Leader,然後將這個候選Leader廣播出去,讓傳送方也能更改自己的後續Leader。同時判斷自己的收集的Notification能否達到超過半數的條件從而決定最終的Leader,如果能決定則設定最終Leader,退出選舉過程。

case LOOKING:   
   // If notification > current, replace and send messages out
   if (n.electionEpoch > logicalclock.get()) {
       logicalclock.set(n.electionEpoch);
       recvset.clear();
       if(totalOrderPredicate(n.leader, n.zxid, n.peerEpoch,
                       getInitId(), getInitLastLoggedZxid(), getPeerEpoch())) {
            updateProposal(n.leader, n.zxid, n.peerEpoch);
       } else {
         updateProposal(getInitId(),getInitLastLoggedZxid(),getPeerEpoch());
       }
       sendNotifications();
   } else if (n.electionEpoch < logicalclock.get()) {
       break;
   } else if (totalOrderPredicate(n.leader, n.zxid, n.peerEpoch,proposedLeader, proposedZxid, proposedEpoch)) {
       updateProposal(n.leader, n.zxid, n.peerEpoch); sendNotifications();
   }
   recvset.put(n.sid, new Vote(n.leader, n.zxid, n.electionEpoch, 
n.peerEpoch));
   if (termPredicate(recvset, new Vote(proposedLeader, proposedZxid,
                            logicalclock.get(), proposedEpoch))) {
      while((n = recvqueue.poll(finalizeWait, TimeUnit.MILLISECONDS)) != null){
         if(totalOrderPredicate(n.leader, n.zxid, n.peerEpoch,proposedLeader, proposedZxid, proposedEpoch)){
             recvqueue.put(n);
             break;
         }
      }
      if (n == null) {
           self.setPeerState((proposedLeader == self.getId()) ?
                                        ServerState.LEADING: learningState());
           Vote endVote = new Vote(proposedLeader, proposedZxid, proposedEpoch);
           leaveInstance(endVote);
        return endVote;
      }
  }
  break;

totalOrderPredicate方法用於判斷對方傳送過來的Vote是不是更新的Leader候選者,如果是的話則更新本地proposedLeader。主要程式碼如下:

protected boolean totalOrderPredicate(long newId, long newZxid, long newEpoch, long curId, long curZxid, long curEpoch) {
        return ((newEpoch > curEpoch) ||
                ((newEpoch == curEpoch) &&  ((newZxid > curZxid) ||
 ((newZxid == curZxid) && (newId > curId)))));
}

termPredicate用於判斷是否滿足選出Leader條件,QuorumVerifier介面的實現,如果滿足則設定Leader,並退出FastLeaderElection過程。主要程式碼如下:

private boolean termPredicate(HashMap<Long, Vote> votes, Vote vote) {
        SyncedLearnerTracker voteSet = new SyncedLearnerTracker();
        voteSet.addQuorumVerifier(self.getQuorumVerifier());
        if (self.getLastSeenQuorumVerifier() != null
                && self.getLastSeenQuorumVerifier().getVersion() > self
                        .getQuorumVerifier().getVersion()) {
            voteSet.addQuorumVerifier(self.getLastSeenQuorumVerifier());
        }
        for (Map.Entry<Long, Vote> entry : votes.entrySet()) {
            if (vote.equals(entry.getValue())) {
                voteSet.addAck(entry.getKey());
            }
        }
        return voteSet.hasAllQuorums();
}

 

B. 回覆Notification的傳送方也是LeaderFollower

如果回覆Notification的傳送方是Leader或者Follower,則流程比較簡單,將Notification訊息儲存到recvset中,並呼叫termPredicate方法判斷是否能確定Leader並結束選舉過程。

程式碼片段:

case FOLLOWING:
case LEADING:
     if(n.electionEpoch == logicalclock.get()){
         recvset.put(n.sid, new Vote(n.leader, n.zxid, n.electionEpoch, 
n.peerEpoch));
         if(termPredicate(recvset, new Vote(n.leader, n.zxid, n.electionEpoch,
n.peerEpoch, n.state))  && checkLeader(outofelection, n.leader, 
n.electionEpoch)) {
            self.setPeerState((n.leader == self.getId()) ?
                                  ServerState.LEADING: learningState());
            Vote endVote = new Vote(n.leader, n.zxid, n.peerEpoch);
            leaveInstance(endVote);
            return endVote;
         }
    }

相關推薦

ZooKeeper系列投票選舉2

ZooKeeper的選舉過程預設使用FastLeaderElection類,FastLeaderElection啟動時啟動Mess

敏捷開發一千零一問系列如何弄清楚專案需求需求開發步驟

這是敏捷開發一千零一問系列的第三十三篇。(在這裡提問,之一,之二,之三,問題總目錄)也是敏捷開發使用者故事系列的第十篇(欄目目錄)。問題需求清晰到什麼程度可以進行開發?一定要弄清楚需求才能開發嗎?怎樣才能弄清楚需求?注意下面的分析是在基於合同的專案開發的語境中的。產品和網際網

《Nodejs開發加密貨幣》DPOS機制分散式共識演算法

前言 共識機制是分散式應用軟體特有的演算法機制。在中心化的軟體裡,再複雜的問題都可以避開使用複雜的演算法邏輯(當然,如果能用演算法統領,程式碼會更加簡潔、高效),在開發設計上可以省卻一定的麻煩。但在分散式軟體開發中,節點間的互操作,節點行為的統一管理,沒有演算

Java並發編程系列CompletionService

xtend cts edate strong ext [] com 喚醒 render CompletionService簡介 CompletionService與ExecutorService類似都可以用來執行線程池的任務,ExecutorService繼承了Execut

Django框架orm一對一的操作

前面的部落格已經對資料庫的基礎知識有了介紹,對資料庫資料一對多的操作有了瞭解,現在來看看資料庫的一對一操作,非常簡單 資料庫中建立兩張表,一個賬戶表,一個使用者表。一個賬戶對應一個使用者,即一對一關係, ORM資料庫的一對一關係:一個表中的一條資料對應著另外一個

敏捷開發一千零一問系列如何進行優先順序排序?

這是敏捷開發一千零一問系列的第二十六篇。(在這裡提問,之一,之二,之三,問題總目錄)問題如何進行優先順序排序?具體故事的優先順序,和版本規劃的優先順序之間有何關係?分析敏捷開發裡邊有很多地方需要多次進行優先順序排序,本文將探討其不同的應用場景,及其關係。值得注意的一點是,敏捷

敏捷開發一千零一問系列計劃撲克就是打不出個結果怎麼辦?

本文是敏捷開發一千零一問的第三十八篇。(欄目總目錄)問題:一個簡單的問題,計劃撲克就是打不出個結果,各持己見怎麼辦?也就是少數人無法說服大家,或者說根本無人去聽回答:計劃撲克的結束條件”近似一致“是個很有趣的標準,其實要回答”什麼時候停止打撲克“,就要先解決”為什麼要打撲克“

敏捷開發一千零一問系列如何做小版本迭代的程式碼管理

本文是敏捷開發一千零一問的第三十五篇。(欄目總目錄)問題若要實現敏捷式的開發,對產品進行迭代式的小版本的釋出,在程式碼管理方面應該怎麼樣管理呢?我們目前的管理是在一個大的版本上不斷的遞增新的需求……但是要是有個需求做到一半,領導又要求做更重要的需求的情況,就很難將開發一半的程

Java併發程式設計系列ThreadLocal

ThreadLocal簡介 ThreadLocal翻譯過來就是執行緒本地變數,初學者可能以為ThreadLocal是指一個Thread,其實說白了,ThreadLocal就是一個成員變數,只不過這是一個特殊的變數——變數值總是與當前執行緒(呼叫Thread.c

IT職場人生系列程式設計師如何增加收入

程式設計師的收入是廣受關注的問題,很多人從業3~5年之後就會遇到這個收入瓶頸。儘管物價不斷上漲,程式設計師尤其是初、中級程式設計師的收入不升反降。即使上次在某個文章中看到有中國第一程式設計師之稱的某位,月薪也只有3萬,儘管這個數字已經很高了,但這個“中國第一”,也只有眾多小型軟體企業總監級別的收入而已。為什麼

算法系列實驗資料與曲線擬合

12.1 曲線擬合12.1.1 曲線擬合的定義        曲線擬合(Curve Fitting)的數學定義是指用連續曲線近似地刻畫或比擬平面上一組離散點所表示的座標之間的函式關係,是一種用解析表示式逼近離散資料的方法。曲線擬合通俗的說法就是“拉曲線”,也就是將現有資料透過

Java併發程式設計系列CountdownLatch

CountDownLatch是JDK提供的併發工具包,理解並掌握這些工具包的使用有助於簡化特定場景下的程式設計。就CountDownLatch而言,允許一個或者多個執行緒等待其他執行緒完成操作。等待其他執行緒完成不是與Thread.join()方法類似嗎,因為T

設計模式系列狀態模式

1.定義 當一個物件內在狀態改變時允許其改變行為,這個物件看起來像改變了其類。 2.通用類圖 角色介紹 State 抽象狀態角色:介面或抽象類,負責物件狀態定義,並且封裝環境角色以實現狀態切換 ConcreteState 具體狀態角色:

Java併發程式設計系列丟失的訊號

這裡的丟失的訊號是指執行緒必須等待一個已經為真的條件,在開始等待之前沒有檢查等待條件。這種場景其實挺好理解,如果一邊燒水,一邊看電視,那麼在水燒開的時候,由於太投入而沒有注意到水被燒開。丟失的訊號指的就是這種情況。 建立兩個執行緒分別執行通知和等待方法,並且將

ZooKeeper系列服務端實現機制

服務端有3種執行方式:leader,follower,observer。leader是領導者,一個ZooKeeper叢集同一時刻最

ZooKeeper系列結束篇

ZooKeeper的設計原理講到這基本就結束了,有興趣的朋友可以下原始碼看看,ZooKeeper的原始碼寫的很好,很輕量級,是入門

算法系列計算中國農曆

        世界各國的日曆都是以天為最小單位,但是關於年和月的演算法卻各不相同,大致可以分為三類:陽曆--以天文年作為日曆的主要週期,例如:中國公曆(格里曆)陰曆--以天文月作為日曆的主要週期,例如:伊斯蘭曆陰陽曆--以天文年和天文月作為日曆的主要週期,例如:中國農曆我國

ZooKeeper系列領導者工作模式

領導者就是Leader,是整個叢集的寫事務流程負責人。 一輪選舉結束時產生新的Leader,並且Epoch加1。同時新的Lead

【Java並發編程】並發新特性—阻塞隊列和阻塞棧含代碼

err 退出 link rac gb2312 com void throws pbo 轉載請註明出處:http://blog.csdn.net/ns_code/article/details/17511147 阻塞隊列 阻塞隊列是Java 5並發新特性中的內容

Python網路程式設計

python基礎之網路程式設計(上篇)   socket程式設計   本篇介紹socket是基於什麼來的,為什麼要知道網際網路底層實現通訊的原理 一、客戶端/服務端架構 即C/S架構,包括 1.硬體C/S架構(印表機) 2.軟體C/S架構(web