前言
在之前的文章中,筆者提過Ozone內部使用的是基於Raft協議的一致性控制,以此保證Ozone Container操作請求在Pipeline節點上的一致性。但是這裏面具體是怎麼樣的一個過程呢?RaftLeader/RaftFollower的一致性同步這塊其實並不是實現在Ozone這個項目裏,而是在外部使用庫Apache Ratis裏的。本文筆者來聊聊Apache Ratis中RaftLeader、RaftFollower是如何做一致性的控制的。
Apache Ratis基於Raft協議的一致性同步過程
在之前的文章Apache Ratis的Ratis Server主從同步機制裏,筆者簡單闡述過其中的主要角色服務和原理過程,過程圖如下所示:
這裏再次引用下前文對於上述服務角色的介紹:
RetryCache,對於客戶端而言,Ratis Sever內部有RetryCache,爲了避免客戶端發起重複的請求操作。
Raft Log,Raft Log可以理解爲是Ratis Server的WAL,每次用戶的操作會被轉化爲transaction log然後被寫入到Raft Log中,如果log寫滿了,則會新創建Raft Log文件進行寫入。
LogAppender,此角色服務是將Leader內最新的transaction log發送到遠端的各個Follower服務中,並獲取返回結果。然後及時更新Raft Log的commit index。
StateMachineUpdater,此角色根據Raft Log當前的commit記錄,及時apply 最新committed的log entry到StateMachine中,然後purge掉commit index之前的log文件。同時它會定期給StateMachine做snapshot操作。StateMachineUpdater的功能有點類似於Checkpoint的角色功能。
不過上面提到的部分粒度比較粗,有很多細節部分沒有提到,比如RaftLeader/RaftFolloer如何認定本地的RaftLog是已經Commit的,又比如說RaftLog和StateMachine間是如何進行交互的等等。下面我們對這裏面的細節內容做更進一步的分析。
Apache Ratis內部RaftLeader/RaftFollower過程調用分析
在本小節的過程調用分析中,我們還是同樣會有上面提到的角色服務:
- RaftLeader/RaftFollower
- RaftLog
- StateMachineUpdater
- StateMachine
在整個RaftLeader/RaftFollower的同步過程中,總共可以拆分爲3個階段:
第一個階段,RaftLeader的本地寫Raft log操作。RaftLeader本地接受到客戶端的操作請求,然後觸發寫本地Raft log操作。在此過程中,StateMachine會進行請求transaction的驗證操作,驗證通過,允許RaftLeader執行寫log entry的操作。RaftLeader在寫log entry時,如果log entry附有額外數據,此部分數據將會被額外寫入StateMachine內。Write log entry如若達到Raft log flush閾值條件時,則會進行一次flush到外部持久存儲的操作,同時觸發StateMachine的flushStateMachineData的操作。OK到此爲止,此過程中新寫入的log還沒有被Committed。
第二個階段,RaftLeader發送append log的請求到RaftFollower上。在此過程中,將會進行如下步驟:
RaftLeader構造append log的請求實例,此過程將會從本地log中讀取log entry數據,如果此log entry數據還帶有StateMachine data的時候,還需要從StateMachine中讀取相應的數據。
RaftLeader發送append log的請求到各個Follower上,Follower接受到請求後,首先更新自身log的Commit Index爲Leader的Commit Index。然後通知本地StateMachineUpdater去apply到最新Commit Index的Raft log。Follower倘若發現接受到的新Raft log和本地不一致時(在重新選舉產生新Leader時,會有可能導致這種請求發生),則進行本地log的truncate操作。如果一致的話,則進行後續本地log entry的寫操作,此過程和第一階段RaftLeader寫本地log的過程類似,也會有StateMachine的相關觸發操作。
這個過程還沒有結束,Follower在執行完本地的log append的操作後,會返回RaftLeader回覆結果,附有Follow本地最新的log信息,包括已經寫出的Index和下一個Index。RaftLeader收到後,判斷是否半數以上已經成功接受到新append的log entry,則進行Commit Index的更新,更新到上次append成功到Follower的那個Index位置上。
第三個階段,RaftLeader通知本地的StateMachineUpdater去apply最新的Raft log到StateMachine中。這個過程RaftLeader和Follower的邏輯是一致的。
在上述過程裏,我們可以看到,RaftLeader是判斷RaftLog是否可以標記爲Commit狀態的確定者,確定的標準爲它本地新寫的Raft log是否已經被半數以上的Follower接受到並寫出到了它們的Raft log內,則進行Leader本地的Commit Index更新。而Follower的Commit則取決於Leader發來的最新的Commit Index。換句話說,Follower只要等RaftLeader告訴我可以更新哪個最新的Commit Index了,然後我就apply最新的log entry到StateMachine中去。
上述的交互過程如下流程圖所示(同一個顏色過程表示屬於同一階段過程):