Cluster集羣模式
前一篇文章 介紹了當配置文件中只有一個server地址時,Standalone模式的啓動流程以及ZooKeeper的節點模型和運行邏輯。在本節中,我會針對Cluster的運行模式進行詳細講解。
啓動流程
public synchronized void start() {
loadDataBase();
cnxnFactory.start();
startLeaderElection();
super.start();
}
QuorumPeerMain::runFromConfig
會構造一個QuorumPeer
對象,並調用start
方法啓動整個Server。
QuorumPeer::start
經過了三個步驟:
- 通過
loadDatabase
將磁盤中的Snapshot和TxnLog加載到內存中,構造一個DataTree
對象 - 啓動一個
ServerCnxnFactory
對象,默認啓動一個Daemon線程運行NIOServerCnxnFactory
,負責接收來自各個Client端的指令。 - 啓動選舉過程
前兩步在Standalone的啓動模式也有出現,我們不再做過多介紹,需要的朋友可以看一看前面的文章。在這裏,我們需要留意第三步。
第三步的名字叫做startLeaderElection
, 看到這個名字,我的第一反應是他會啓動一個獨立線程去負責Leader的選舉,但其實不然。通過源碼走讀,我們看到startLeaderElection
中其實只是對選舉做了初始化設定,真正的選舉線程其實是在QuorumPeer
這個線程類中啓動的。
選舉算法我在這裏先不做過多介紹,這會是一個獨立的文章,放在下一篇進行講解,讓我們先看一下選舉之後的狀態。
ZooKeeper Server的三種狀態
Cluster模式顧名思義是由多個server節點組成的一個集羣,在集羣中存在一個唯一的leader節點負責維護節點信息,其他節點只負責接收轉發Client請求,或者更甚,只是監聽Leader的變化狀態。
在配置文件中,我們可以通過peerType
對當前節點類型進行配置。目前支持兩種類型:
PARTICIPANT
: participant 具有選舉權和被選舉權,可以被選舉成爲Leader,如果未能成功被選舉,則成爲Follower。OBSERVER
: observer只具備選舉權,他可以投票選舉Leader,但是他本身只能夠成爲observer監聽leader的變化。
選舉完成之後,節點從PARTICIPANT
和OBSERVER
兩種狀態變成了LEADER
,FOLLOWER
和OBSERVER
三種狀態,每種狀態對應一個ZooKeeperServer
子類。
選舉結束之後,三種類型的節點根據自身的類型進入啓動流程,啓動對應的ZooKeeperServer
。
- Leader 作爲整個集羣中的主節點,會啓動一個
LearnerCnxAcceptor
的線程負責同其他節點進行通信。 - Follower和Observer的大致邏輯類似,首先通過配置信息連接上Leader節點,再向Leader節點發送ACK請求,告知鏈接成功。
- 當Leader中檢測到大部分的Follower都已經成功鏈接到Leader之前,socket訪問會被阻塞;直到檢測到大部分Follower鏈接上之後,才退出阻塞狀態,令
Leader
,Follower
和Observer
啓動對應的ZooKeeperServer
。
維護節點的一致性
上圖中使用黃色的節點表示 Observer
上的操作,藍色的節點表示 Follower
中的操作,紫色的節點表示 Leader
上的操作。
如圖所示,不論是Follower
還是Observer
在接受到Request
請求後,都通過一個RequestProcessor
將請求分發給Leader進行處理。單節點的處理邏輯能夠保證數據在各個節點是一致的。
在Leader
中,通過proposal
方法將需要提交的Request
加入 outstandingProposals
隊列。
每個Follower
或者Observer
同Leader
建立鏈接之後會創建一個LearnerHandler
線程,對於Follower
類型的LearnerHandler
,在線程的循環中,會將outstandingProposals
中的Request
請求分發回對應的Follower
進行消費,消費完畢後,再通過 SendAckRequestProcessor
發回 Leader
。
在 Leader
的任務鏈中存在一個AckRequestProcessor
節點,監聽Follower
響應的結果,當大部分Follower
都響應了某次提交之後,會認爲該提交有效,再通過 CommitProcessor
正式提交到內存中。
LeaderZooKeeperServer
和Standalone模式的ZooKeeperServer
一樣,在LeaderZooKeeperServer
中也是通過一個RequestProcessor
任務鏈處理來自Client的請求。
- PrepRequestProcessor: 在outstandingChanges中創建臨時節點,便於後續請求快速訪問,詳細解析請參看上一篇文章
- ProposalRequestProcessor: 在Proposal的構造方法中,會傳入一個RequestProcessor對象,同時他自身也會構造一個包裹了
ACKRequestProcessor
的SyncRequestProcessor
對象。如上圖所示,ProposalRequestProcessor
在nextProcessor
消費了Request之後,還會使用SyncReqeustProcessor
進行二次消費,這使得任務在這個節點產生了兩個分支。 - CommitProcessor: 本節點運行在一個獨立線程中,每一次輪詢都會將
queuedRequests
中的請求信息加入toProcess
隊列中,然後在輪詢的開始處,對toProcess
隊列進行批量處理。在方法內部有一個局部變量nextPending
保存從queuedRequests
取出的最後一個Request
請求,如果nextPending
遲遲沒有被提交,則進入等待的邏輯,與此同時,queuedRequests
會一直積累請求信息,直到nextPending
的請求通過CommitProcessor::commit
被提交到committedRequests
中,才能夠退出等待邏輯,批量消費queuedRequests
中的請求數據。 - ToBeAppliedRequestProcessor:調用FinalRequestProcessor進行處理,並將請求從
toBeApplied
隊列中移除。 - FinalRequestProcessor:將請求信息合併到
DataTree
中,具體操作見前一篇文章 - SyncRequestProcessor: 如上一節所說,在
SyncRequestProcessor
中會將Request
請求封裝成一個Transaction
,並寫入 TxnLog, 同時定期備份 Snapshot。 - ACKRequestProcessor: 在這個節點中,通過執行
leader:processAck
檢查滿足條件的request請求,調用CommitProcessor::commit
將滿足響應條件的Request
提交給CommitProcessor
處理。
FollowerZooKeeperServer
如圖,和之前的任務鏈不同,在FollowerZooKeeperServer
中同時存在兩個並行的任務鏈處理。
第一個任務鏈負責將Follower
接受到的Request
請求分發給 Leader
第二個任務鏈通過接收 Leader
轉發來的 Request
請求,當數據被同步到 disk 之後,通過 SendAckRequestProcessor 將接收結果反饋給 Leader
。
ObserverZooKeeperServer
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 負責整個節點的維護。
Follower
和 Observer
會將自己接收到的 Request
分發給 Leader
進行消費,在Leader
中會通過內部廣播將請求信息通知回Follower
。
Cluster 模式和 Standalone 模式在節點模型方面沒有什麼區別,主要就是在RequestProcessor
的任務鏈邏輯更加複雜,需要通過ProposalRequestProcessor
將數據分發給集羣中的Follower
,當大多數Follower
都認可記錄後,纔將Transaction Log 同步到 DataTree 中,他的處理細節會更加的複雜。