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
經過了三個步驟:
- 通過
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 中,他的處理細節會更加的複雜。