zk選舉流程分析

zk集羣運行過程當中,服務器選舉的源碼剖析

在zk服務器集羣啓動過程當中,經QuorumPeerMain中,不光會建立ZooKeeperServer對象,同時會生成QuorumPeer對象,表明了ZooKeeper集羣中的一臺機器。在整個機器運行期間,負責維護該機器的運行狀態,同時會根據狀況發起Leader選舉。下圖是 《從PAXOS到ZOOKEEPER分佈式一致性原理與實踐》的服務器啓動流程算法

QuorumPeer是一個獨立的線程,維護着zk機器的狀態。服務器

@Override
public synchronized void start() {
    loadDataBase();
    cnxnFactory.start();        
    startLeaderElection();
    super.start();
}

本次主要介紹的是選舉相關的內容,至於其餘操做能夠看其餘博客。以後的行文都是從startLeaderElection中衍生出來的。網絡

基本概念:

SID:服務器ID,用來標示ZooKeeper集羣中的機器,每臺機器不能重複,和myid的值一直
ZXID:事務ID
Vote: 選票,具體的數據結構後面有
Quorum:過半機器數
選舉輪次:logicalclock,zk服務器Leader選舉的輪次

服務器類型:數據結構

在zk中,引入了Leader、Follwer和Observer三種角色。zk集羣中的全部機器經過一個Leader選舉過程來選定一臺被稱爲Leader的機器,Leader服務器爲客戶端提供讀和寫服務。Follower和Observer都可以提供讀服務,惟一的區別在於,Observer機器不參與Leader選舉過程,也不參與寫操做的過半寫成功策略。所以,Observer存在的意義是:在不影響寫性能的狀況下提高集羣的讀性能。架構

服務器狀態:分佈式

+ LOOKING:Leader選舉階段
+ FOLLOWING:Follower服務器和Leader保持同步狀態
+ LEADING:Leader服務器做爲主進程領導狀態。
+ OBSERVING:觀察者狀態,代表當前服務器是Observer,不參與投票

選舉的目的就是選擇出合適的Leader機器,由Leader機器決定事務性的Proposal處理過程,實現類兩階段提交協議(具體是ZAB協議)ide

QuorumPeer維護集羣機器狀態

QuorumPeer的職責就是不斷地檢測當前的zk機器的狀態,執行對應的邏輯,簡單來講,就是根據服務所處的不一樣狀態執行不一樣的邏輯。刪除了一部分邏輯後,代碼以下:性能

@Override
public void run() {
    setName("QuorumPeer" + "[myid=" + getId() + "]" +
            cnxnFactory.getLocalAddress());

     try {
        while (running) {
            switch (getPeerState()) {
            case LOOKING:
                LOG.info("LOOKING");
                try {
                    setBCVote(null);
                    setCurrentVote(makeLEStrategy().lookForLeader());
                } catch (Exception e) {
                    LOG.warn("Unexpected exception", e);
                    setPeerState(ServerState.LOOKING);
                }
                
                break;
            case OBSERVING:
                try {
                    LOG.info("OBSERVING");
                    setObserver(makeObserver(logFactory));
                    observer.observeLeader();
                } catch (Exception e) {
                    LOG.warn("Unexpected exception",e );                        
                } finally {
                    observer.shutdown();
                    setObserver(null);
                    setPeerState(ServerState.LOOKING);
                }
                break;
            case FOLLOWING:
                try {
                    LOG.info("FOLLOWING");
                    setFollower(makeFollower(logFactory));
                    follower.followLeader();
                } catch (Exception e) {
                    LOG.warn("Unexpected exception",e);
                } finally {
                    follower.shutdown();
                    setFollower(null);
                    setPeerState(ServerState.LOOKING);
                }
                break;
            case LEADING:
                LOG.info("LEADING");
                try {
                    setLeader(makeLeader(logFactory));
                    leader.lead();
                    setLeader(null);
                } catch (Exception e) {
                    LOG.warn("Unexpected exception",e);
                } finally {
                    if (leader != null) {
                        leader.shutdown("Forcing shutdown");
                        setLeader(null);
                    }
                    setPeerState(ServerState.LOOKING);
                }
                break;
            }
        }
    } finally {
        LOG.warn("QuorumPeer main thread exited");
    }
}

當機器處於LOOKING狀態時,QuorumPeer會進行選舉,可是具體的邏輯並非由QuorumPeer來負責的,總體的投票過程獨立出來了,從邏輯執行的角度看,整個過程設計到兩個主要的環節:this

  • 與其餘的zk集羣通訊的過程
  • 實現具體的選舉算法

而QuorumPeer中默認使用的選舉算法是FastLeaderElection,以後的分析也是基於FastLeaderElection而言的。線程

選舉過程當中的總體架構

在集羣啓動的過程當中,QuorumPeer會根據配置實現不一樣的選舉策略 this.electionAlg = createElectionAlgorithm(electionType);

protected Election createElectionAlgorithm(int electionAlgorithm){
    Election le=null;

    switch (electionAlgorithm) {

    case 3:
        QuorumCnxManager qcm = new QuorumCnxManager(this);
        QuorumCnxManager.Listener listener = qcm.listener;
        if(listener != null){
            listener.start();
            le = new FastLeaderElection(this, qcm);
        } else {
            LOG.error("Null listener when initializing cnx manager");
        }
        break;
    default:
        assert false;
    }
    return le;
}

若是ClientCnxn是zk客戶端中處理IO請求的管理器,QuorumCnxManager是zk集羣間負責選舉過程當中網絡IO的管理器,在每臺服務器啓動的時候,都會啓動一個QuorumCnxManager,用來維持各臺服務器之間的網絡通訊。

對於每一臺zk機器,都須要創建一個TCP的端口監聽,在QuorumCnxManager中交給Listener來處理,使用的是Socket的阻塞式IO(默認監聽的端口是3888,是在config文件裏面設置的)。在兩兩相互鏈接的過程當中,爲了不兩臺機器之間重複地建立TCP鏈接,zk制定了鏈接的規則:只容許SID打的服務器主動和其餘服務器創建鏈接。實現的方式也比較簡單,在receiveConnection中,服務器會對比與本身創建鏈接的服務器的SID,判斷是否接受請求,若是本身的SID更大,那麼會斷開鏈接,而後本身主動去和遠程服務器創建鏈接。這段邏輯是由Listener來作的,且Listener獨立線程,receivedConnection,創建鏈接後的示意圖:

QuorumCnxManager是鏈接的管家,具體的TCP鏈接交給了Listener,可是對於選票的管理,內部還維護了一系列的隊列:

  • recvQueue:消息接收隊列,用來存放那些從其餘服務器接收到的消息,單獨的隊列
  • 分組隊列(quorumCnxManager中將zk集羣中的每臺機器按照SID單獨分組造成隊列集合):
    • queueSendMap:消息發送隊列,用於保存待發送的消息。new ConcurrentHashMap<Long, ArrayBlockingQueue >(); 按照SID分組,分別爲每臺機器分配一個單獨隊列,保證各臺機器之間的消息發放互不影響
    • senderWorderMap:發送器集合。每一個SendWorder消息發送器,都對應一臺遠程zk服務器,負責消息的發放。
    • lastMessageSent:最近發送過的消息,按照SID分組

基本的通訊流程以下:

以上內容主要是創建各臺zk服務器之間的鏈接通訊過程,具體的選舉策略zk抽象成了 Election ,主要分析的是FastLeaderElection方式(選舉算法的核心部分):

public interface Election {
    public Vote lookForLeader() throws InterruptedException;
    public void shutdown();
}

FastLeaderElection選舉算法

上面說過QuorumPeer檢測到當前服務器的狀態是LOOKING的時候,就會進行新一輪的選舉,經過 setCurrentVote(makeLEStrategy().lookForLeader());也就是FastLeaderElection的lookForLeader來進行初始選擇,實現的方式也很簡單,主要的邏輯在FastLeaderElection.lookForLeader中實現:

基本流程先說明一下:

  • QuorumPeer會輪詢檢查當前服務器狀態,若是發現State是LOOKING,調用Election的lookForLeader來開始新一輪的選舉
  • FastLeaderElection會首先將logicallock++,表示新的一輪選舉開始了
  • 構造初始的選票,Vote的內容就是選本身,而後通知zk集羣中的其餘機器
  • FastLeaderElection會一直輪詢查狀態,只要是LOOKING態,就會從recvqueue中獲取其餘服務器同步的選票信息,爲了方便說明,記錄爲n
  • 根據n的票選信息狀態,作相關的操做
    • LOOKING: 都處於無Leader態,比較一下選票的優劣,看是否更新本身的選票,若是更新了就同時通知給其餘服務器
    • FOLLOWING、LEADING:說明集羣中已經有Leader存在,更新一下本身的狀態,結束本輪投票
    • OBSERVING:這票沒什麼卵用,直接捨棄(OBSERVER是不參與投票的)

根據上面的流程,能夠大概說明一下FasterLeaderElection肯定選票更優的策略:

  • 若是外部投票中被推舉的Leader服務器選舉輪次大於自身的輪次,那麼就更新選票
  • 若是選舉輪次一致,就對比二者的ZXID,ZAB協議中ZXID越大的留存的信息也越多,所以若是ZXID大於本身的,那麼就更新選票
  • 若是ZXID也一致,對比二者的SID,SID大,則優先級高

總結:

以上就是zk的默認選票流程,按照ZAB協議的兩種狀態分析:

  • 初始化的時候,處於同一輪次進行投票直到投票選擇出一個Leader
  • 崩潰恢復階段:
    • Leader服務器掛了,那麼經歷的和初始化流程相似的過程,選擇Leader
    • Follower服務器掛了,那麼本身在執行選舉的過程當中,會收到其餘服務器給的Leader選票信息,也能夠肯定Leader所屬
相關文章
相關標籤/搜索