ZooKeeper源碼學習筆記(3)--Cluster模式下的ZooKeeper

Cluster集羣模式

判斷啓動模式

前一篇文章 介紹了當配置文件中只有一個server地址時,Standalone模式的啓動流程以及ZooKeeper的節點模型和運行邏輯。在本節中,我會針對Cluster的運行模式進行詳細講解。

啓動流程

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

QuorumPeerMain::runFromConfig會構造一個QuorumPeer對象,並調用start方法啓動整個Server。

QuorumPeer::start經過了三個步驟:

  1. 通過loadDatabase將磁盤中的Snapshot和TxnLog加載到內存中,構造一個DataTree對象
  2. 啓動一個ServerCnxnFactory對象,默認啓動一個Daemon線程運行NIOServerCnxnFactory,負責接收來自各個Client端的指令。
  3. 啓動選舉過程

前兩步在Standalone的啓動模式也有出現,我們不再做過多介紹,需要的朋友可以看一看前面的文章。在這裏,我們需要留意第三步。

第三步的名字叫做startLeaderElection, 看到這個名字,我的第一反應是他會啓動一個獨立線程去負責Leader的選舉,但其實不然。通過源碼走讀,我們看到startLeaderElection中其實只是對選舉做了初始化設定,真正的選舉線程其實是在QuorumPeer這個線程類中啓動的。

選舉算法我在這裏先不做過多介紹,這會是一個獨立的文章,放在下一篇進行講解,讓我們先看一下選舉之後的狀態。

ZooKeeper Server的三種狀態

Cluster模式顧名思義是由多個server節點組成的一個集羣,在集羣中存在一個唯一的leader節點負責維護節點信息,其他節點只負責接收轉發Client請求,或者更甚,只是監聽Leader的變化狀態。

ZooKeeper Server的三種狀態

在配置文件中,我們可以通過peerType對當前節點類型進行配置。目前支持兩種類型:

  1. PARTICIPANT: participant 具有選舉權和被選舉權,可以被選舉成爲Leader,如果未能成功被選舉,則成爲Follower。
  2. OBSERVER: observer只具備選舉權,他可以投票選舉Leader,但是他本身只能夠成爲observer監聽leader的變化。

選舉完成之後,節點從PARTICIPANTOBSERVER兩種狀態變成了LEADER,FOLLOWEROBSERVER三種狀態,每種狀態對應一個ZooKeeperServer子類。

節點啓動圖

選舉結束之後,三種類型的節點根據自身的類型進入啓動流程,啓動對應的ZooKeeperServer

  1. Leader 作爲整個集羣中的主節點,會啓動一個 LearnerCnxAcceptor 的線程負責同其他節點進行通信。
  2. Follower和Observer的大致邏輯類似,首先通過配置信息連接上Leader節點,再向Leader節點發送ACK請求,告知鏈接成功。
  3. 當Leader中檢測到大部分的Follower都已經成功鏈接到Leader之前,socket訪問會被阻塞;直到檢測到大部分Follower鏈接上之後,才退出阻塞狀態,令Leader,FollowerObserver啓動對應的ZooKeeperServer

維護節點的一致性

節點的一致性

上圖中使用黃色的節點表示 Observer 上的操作,藍色的節點表示 Follower 中的操作,紫色的節點表示 Leader 上的操作。

如圖所示,不論是Follower還是Observer在接受到Request請求後,都通過一個RequestProcessor將請求分發給Leader進行處理。單節點的處理邏輯能夠保證數據在各個節點是一致的。

Leader中,通過proposal方法將需要提交的Request加入 outstandingProposals 隊列。

每個Follower或者ObserverLeader建立鏈接之後會創建一個LearnerHandler線程,對於Follower類型的LearnerHandler,在線程的循環中,會將outstandingProposals中的Request請求分發回對應的Follower 進行消費,消費完畢後,再通過 SendAckRequestProcessor 發回 Leader

Leader 的任務鏈中存在一個AckRequestProcessor節點,監聽Follower 響應的結果,當大部分Follower都響應了某次提交之後,會認爲該提交有效,再通過 CommitProcessor 正式提交到內存中。

LeaderZooKeeperServer

Leader的RequestProcessor任務鏈

和Standalone模式的ZooKeeperServer一樣,在LeaderZooKeeperServer中也是通過一個RequestProcessor任務鏈處理來自Client的請求。

  1. PrepRequestProcessor: 在outstandingChanges中創建臨時節點,便於後續請求快速訪問,詳細解析請參看上一篇文章
  2. ProposalRequestProcessor: 在Proposal的構造方法中,會傳入一個RequestProcessor對象,同時他自身也會構造一個包裹了 ACKRequestProcessorSyncRequestProcessor對象。如上圖所示,ProposalRequestProcessornextProcessor消費了Request之後,還會使用SyncReqeustProcessor進行二次消費,這使得任務在這個節點產生了兩個分支。
  3. CommitProcessor: 本節點運行在一個獨立線程中,每一次輪詢都會將 queuedRequests 中的請求信息加入toProcess隊列中,然後在輪詢的開始處,對toProcess隊列進行批量處理。在方法內部有一個局部變量 nextPending 保存從 queuedRequests 取出的最後一個 Request 請求,如果nextPending遲遲沒有被提交,則進入等待的邏輯,與此同時,queuedRequests會一直積累請求信息,直到nextPending的請求通過 CommitProcessor::commit 被提交到 committedRequests 中,才能夠退出等待邏輯,批量消費queuedRequests 中的請求數據。
  4. ToBeAppliedRequestProcessor:調用FinalRequestProcessor進行處理,並將請求從toBeApplied隊列中移除。
  5. FinalRequestProcessor:將請求信息合併到DataTree中,具體操作見前一篇文章
  6. SyncRequestProcessor: 如上一節所說,在SyncRequestProcessor中會將Request 請求封裝成一個Transaction,並寫入 TxnLog, 同時定期備份 Snapshot。
  7. ACKRequestProcessor: 在這個節點中,通過執行leader:processAck 檢查滿足條件的request請求,調用CommitProcessor::commit 將滿足響應條件的 Request 提交給 CommitProcessor 處理。

FollowerZooKeeperServer

Follower的RequestProcessor任務鏈

如圖,和之前的任務鏈不同,在FollowerZooKeeperServer中同時存在兩個並行的任務鏈處理。

第一個任務鏈負責將Follower接受到的Request請求分發給 Leader

第二個任務鏈通過接收 Leader 轉發來的 Request 請求,當數據被同步到 disk 之後,通過 SendAckRequestProcessor 將接收結果反饋給 Leader

ObserverZooKeeperServer

Observer的RequestProcessor任務鏈

Observer 中的任務結構和 Follower中的第一個任務鏈很相似。都是負責把接收到的 Request 請求轉發給 Leader

集羣間的交互

節點心跳

Leader::lead() 中有一個 while 循環負責維持 Leader 和其他節點之間的心跳關係。

while (true) {
  Thread.sleep(self.tickTime / 2);
  for (LearnerHandler f : getLearners()) {
    f.ping();
  }
  if (!tickSkip && !self.getQuorumVerifier().containsQuorum(syncedSet)) {
    shutdown("Not sufficient followers synced, only synced with sids: [ " + getSidSetString(syncedSet) + " ]");
    return;
  }
}

可以看到,在 while 循環中,每個tick週期中會觸發兩次心跳。

每次心跳都是由 Leader 主動發送給各個節點,節點中也存在一個while 循環讀取socket 中的信息。

while (this.isRunning()) {
  readPacket(qp);
  processPacket(qp);
}

processPacket中,會處理接收到的 QuorumPacket 對象,當接受到心跳信息 PING 時,同Leader進行一次socket交互,告知存活。

sync 同步

Client 可以通過API接口發送一個sync信號給集羣中的任意節點。 不論Leader 是直接或是間接接收到 sync 信號,都會將這個信號通過集羣內部的 socket 鏈接分發給各個節點。

Follower或者Observer 接收到集羣廣播的 sync 信號時,會在內部調用 commit 方法,確保 CommitProcessor 能夠調用 FinalRequestProcessor 將Transaction Log 合併到 DataTree 中。

Leader異常的重新選主

如果因爲 Leader 異常導致集羣中的其他節點無法正常訪問Leader,則會重新進入選主的流程。

我們在 QuorumPeer 中看到,不論是 Leader , Follower 還是 Observer 他們在出現異常之後,都會 setPeerState(ServerState.LOOKING),從而進入選主流程。

總結

ZooKeeper 的 Cluster 模式中,允許我們同時啓動多個 ZooKeeper 服務。 在整個集羣中會選出一個Leader Server 負責整個節點的維護。

FollowerObserver 會將自己接收到的 Request 分發給 Leader 進行消費,在Leader 中會通過內部廣播將請求信息通知回Follower

Cluster 模式和 Standalone 模式在節點模型方面沒有什麼區別,主要就是在RequestProcessor的任務鏈邏輯更加複雜,需要通過ProposalRequestProcessor將數據分發給集羣中的Follower,當大多數Follower都認可記錄後,纔將Transaction Log 同步到 DataTree 中,他的處理細節會更加的複雜。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章