天天看點

ZooKeeper源碼閱讀(五)---服務端叢集流程

從本文開始正式開始學習zk中核心叢集的實作原理。 

1、單機調試叢集方法

首先建立三個zoodata目錄,并将預設zoo.cfg檔案拷貝進去

ZooKeeper源碼閱讀(五)---服務端叢集流程

 單獨配置每個cfg檔案:

dataDir[持久化檔案與myid檔案儲存的位址,每個配置檔案均不同],clientPort[用戶端接口端口,單機測試保證均不相同],server[叢集服務位址,配置相同],最後在各自的dataDir目錄下建立myid檔案[内容為0,1,2編号,與cfg裡server.編号 相同]。之後就可以idea配置不同的啟動參數,進行叢集的調試了。

dataDir=/zookeeper/apache-zookeeper-3.7.0/conf/zoocfg/zoo3
clientPort=2183
server.0=127.0.0.1:2888:3888
server.1=127.0.0.1:2889:3889
server.2=127.0.0.1:2890:3890
           

2、啟動流程

首先我們開啟三個server,然後關閉一個,打上斷點重新開啟服務,逐漸調試

//首先差別于單機,叢集的入口變為了
runFromConfig(){

    quorumPeer.initialize();

    quorumPeer.start();

}

quorumPeer.start(){
    //加載磁盤資料
    loadDataBase();
    //開啟io服務
    startServerCnxnFactory();
    //進行選舉
    startLeaderElection();
    //peer主循環
    super.start();
}

startLeaderElection(){
    this.electionAlg = createElectionAlgorithm(electionType);
}

createElectionAlgorithm(){
    //這個版本隻剩case 3 的選舉方法了
    //伺服器之間用于選舉的連接配接管理類
    createCnxnManager();
    QuorumCnxManager.Listener listener = qcm.listener;
    //與其他服務建立連接配接與監聽
    listener.start();
    FastLeaderElection fle = new FastLeaderElection(this, qcm);
    //開啟選舉算法消息收發線程
    fle.start();

}
           

3、選舉算法

首先選舉算法的實作,3.7這個版本用于選舉的算法隻剩FastLeaderElection這個類了,這裡深入學習一下,算法參數我們就先不深究了,先來看看整體的架構,可以看到整體的邏輯是FastLeaderElection類維護了與其他peer的連接配接,并處理收發消息(均是通過隊列+線程的異步形式),發送消息就是簡單的從隊列中取出消息然後發送,而接收消息實作邏輯較為複雜,主要是針對收發方不同的節點狀态以及選票資訊進行選票的輪換,這裡我們簡化一下:

public class FastLeaderElection implements Election {
    //與其他服務端的TCP連接配接管理,處理實際io的收發
   QuorumCnxManager manager;
    //發送選票消息隊列---tosend可以是Notification或者ack
    LinkedBlockingQueue<ToSend> sendqueue;
    //接收選票消息隊列---Notification為選票改變消息
    LinkedBlockingQueue<Notification> recvqueue;
    //負責實際收發消息的處理類
    Messenger messenger;
}
//内部類,處理算法層面的收發選票
protected class Messenger {
    
   
   WorkerSender{
        
        run(){
            //發送消息,添加進發送隊列
            recvqueue.add()
        }        

    }

    WorkerReceiver{
        
        run(){

            response = manager.pollRecvQueue(3000, TimeUnit.MILLISECONDS);
            1、參數校驗
            2、都不為Looking則将目前的選舉結果傳回給發送方
            3、若都為Looking根據接收到的選舉輪次與最大事務id,分别執行不同的算法流程
                +大體規則為:忽略選舉輪次小的消息,給選舉大的消息投票,若相同則比較zxid,sid大的優先
        }
    }
}
           

那麼選舉是如何開始的呢,從上文在伺服器啟動過程中,會開啟選舉相關連接配接與線程。在zk中會在兩種情況下開始選舉:1、節點啟動  2、follower與leader丢失連接配接。這裡我們先看一下在2小節略過的peer主loop,在LOOKING狀态會調用lookForLeader進行選舉。

while (running) {

case LOOKING:
    //開啟選舉
    startLeaderElection();
    setCurrentVote(makeLEStrategy().lookForLeader());
case OBSERVING:
    setObserver(makeObserver(logFactory));
    observer.observeLeader();
case FOLLOWING:
    setFollower(makeFollower(logFactory));
    follower.followLeader();
case LEADING:
    setLeader(makeLeader(logFactory));
    leader.lead();

}
           

FastLeaderElection的主要選舉邏輯在lookForLeader方法裡,這裡就直接偷一張流程圖了,代碼整體邏輯比較複雜:

ZooKeeper源碼閱讀(五)---服務端叢集流程

4、peer主loop

從上節可以看到peer會根據節點狀态進入不同的處理邏輯,并一直循環下去。新的服務啟動之後LOOKING狀态,會發起一次選舉流程。待選舉完成之後,則各自進入following與leading流程,這裡從leader視角分析以下主要流程:

leader流程

1、啟用新的epoch,并與每個follower建立連接配接,連接配接處理采用LearnerHandler線程

2、發送NEWLEADER包,并等待follower完成同步

3、周期發送ping給follower維持連接配接

4、LearnerHandler線程處理具體業務邏輯互動主要是是否進行資料同步與ACK互動

5、對外client請求的處理也采用責任鍊模式,為以下這些

PrepRequestProcessor:建立和修改狀态的Request關聯的header和txn

ProposalRequestProcessor:将寫請求發送proposal到所有的follower

SyncRequestProcessor:将發出去的proposal批量寫入磁盤

AckRequestProcessor:當proposal真正寫入了磁盤後,向本機發送ack包

CommitProcessor:比對本地submitted的請求和收到的committed的請求

ToBeAppliedRequestProcessor:把寫入到磁盤的proposal從toBeApplied中删除

finalProcessor:把commit的proposal寫入到本機的記憶體狀态中