上一篇 源碼分析 RocketMQ DLedger(多副本) 之日誌複製(傳播) ,可能有很多讀者朋友們以爲源碼閱讀較爲枯燥,看的有點雲裏霧裏,本篇將首先梳理一下 RocketMQ DLedger 多副本關於日誌複製的三個核心流程圖,而後再思考一下在異常狀況下如何保證數據一致性。網絡
上圖是一個簡易的日誌複製的模型:圖中客戶端向 DLedger 集羣發起一個寫請求,集羣中的 Leader 節點來處理寫請求,首先數據先存入 Leader 節點,而後須要廣播給它的全部從節點,從節點接收到 Leader 節點的數據推送對數據進行存儲,而後向主節點彙報存儲的結果,Leader 節點會對該日誌的存儲結果進行仲裁,若是超過集羣數量的一半都成功存儲了該數據,主節點則向客戶端返回寫入成功,不然向客戶端寫入寫入失敗。併發
接下來咱們來探討日誌複製的核心設計要點。app
爲了方便對日誌進行管理與辨別,raft 協議爲一條一條的消息進行編號,每一條消息達到主節點時會生成一個全局惟一的遞增號,這樣能夠根據日誌序號來快速的判斷數據在主從複製過程當中數據是否一致,在 DLedger 的實現中對應 DLedgerMemoryStore 中的 ledgerBeginIndex、ledgerEndIndex,分別表示當前節點最小的日誌序號與最大的日誌序號,下一條日誌的序號爲 ledgerEndIndex + 1 。源碼分析
與日誌序號還與一個概念綁定的比較緊密,即當前的投票輪次。.net
請思考以下問題,Leader 節點收到客戶端的數據寫入請求後,經過解析請求,提取數據部分,構建日誌對象,並生成日誌序號,用 seq 表示,而後存儲到 Leader 節點中,而後將日誌廣播(推送)到其從節點,因爲這個過程當中存在網絡時延,若是此時客戶端向主節點查詢 seq 的日誌,因爲日誌已經存儲在 Leader 節點中了,若是直接返回給客戶端顯然是有問題的,那該如何來避免這種狀況的發生呢?線程
爲了解決上述問題,DLedger 的實現(應該也是 raft 協議的一部分)引入了已提交指針(committedIndex)。即當主節點收到客戶端請求時,首先先將數據存儲,但此時數據是未提交的,此過程能夠稱之爲追加,此時客戶端沒法訪問,只有當集羣內超過半數的節點都將日誌追加完成後,纔會更新 committedIndex 指針,得以是數據可否客戶端訪問。設計
一條日誌要能被提交的充分必要條件是日誌獲得了集羣內超過半數節點成功追加,才能被認爲已提交。指針
從上文得知,一個擁有3個節點的 DLedger 集羣,只要主節點和其中一個從節點成功追加日誌,則認爲已提交,客戶端便可經過主節點訪問。因爲部分數據存在延遲,在 DLedger 的實現中,讀寫請求都將由 Leader 節點來負責。那落後的從節點如何再次跟上集羣的步驟呢?日誌
要從新跟上主節點的日誌記錄,首先要知道的是如何判斷從節點已丟失數據呢?中間件
DLedger 的實現思路是,DLedger 會按照日誌序號向從節點源源不斷的轉發日誌,從節點接收後將這些待追加的數據放入一個待寫隊列中。關鍵中的關鍵:從節點並非從掛起隊列中處理一個一個的追加請求,而是首先查閱從節點當前已追加的最大日誌序號,用 ledgerEndIndex 表示,而後嘗試追加 (ledgerEndIndex + 1)的日誌,用該序號從代寫隊列中查找,若是該隊列不爲空,而且沒有 (ledgerEndIndex + 1)的日誌條目,說明從節點未接收到這條日誌,發生了數據缺失。而後從節點在響應主節點 append 的請求時會告知數據不一致,而後主節點的日誌轉發線程其狀態會變動爲COMPARE,將向該從節點發送COMPARE命令,用來比較主從節點的數據差別,根據比較的差別從新從主節點同步數據或刪除從節點上多餘的數據,最終達到一致。於此同時,主節點也會對PUSH超時推送的消息發起重推,盡最大可能幫助從節點及時更新到主節點的數據。
推薦閱讀:RocketMQ 日誌複製系列文章:
一、源碼分析 RocketMQ DLedger 多副本存儲實現
二、源碼分析 RocketMQ DLedger(多副本) 之日誌追加流程
三、源碼分析 RocketMQ DLedger(多副本) 之日誌複製(傳播)
> 做者簡介:《RocketMQ技術內幕》做者,RocketMQ 社區佈道師,維護公衆號:中間件興趣圈,重點關注JAVA集合、JAVA併發包、Netty、Dubbo、RocketMQ、Mybatis、Elasticsearch、Netty。可掃描以下二維碼與做者進行互動。