RocketMQ原理學習--多副本實現 DLedger

一、DLedger引入目的

 


在 RocketMQ 4.5 版本之前,RocketMQ 只有 Master/Slave 一種部署方式,一組 broker 中有一個 Master ,有零到多個
Slave,Slave 通過同步複製或異步複製的方式去同步 Master 數據。Master/Slave 部署模式,提供了一定的高可用性。
但這樣的部署模式,有一定缺陷。比如故障轉移方面,如果主節點掛了,還需要人爲手動進行重啓或者切換,無法自動將一個從節點轉換爲主節點。因此,我們希望能有一個新的多副本架構,去解決這個問題。

新的多副本架構首先需要解決自動故障轉移的問題,本質上來說是自動選主的問題。這個問題的解決方案基本可以分爲兩種:

  • 利用第三方協調服務集羣完成選主,比如 zookeeper 或者 etcd。這種方案會引入了重量級外部組件,加重部署,運維和故障診斷成本,比如在維護 RocketMQ 集羣還需要維護 zookeeper 集羣,並且 zookeeper 集羣故障會影響到 RocketMQ 集羣。
  • 利用 raft 協議來完成一個自動選主,raft 協議相比前者的優點是不需要引入外部組件,自動選主邏輯集成到各個節點的進程中,節點之間通過通信就可以完成選主。

因此最後選擇用 raft 協議來解決這個問題,而 DLedger 就是一個基於 raft 協議的 commitlog 存儲庫,也是 RocketMQ 實現新的高可用多副本架構的關鍵。

二、DLedger 設計理念

1. DLedger 定位

 


Raft 協議是複製狀態機的實現,這種模型應用到消息系統中就會存在問題。對於消息系統來說,它本身是一箇中間代理,commitlog 狀態是系統最終狀態,並不需要狀態機再去完成一次狀態構建。因此 DLedger 去掉了 raft 協議中狀態機的部分,但基於raft協議保證commitlog 是一致的,並且是高可用的。

 


另一方面 DLedger 又是一個輕量級的 java library。它對外提供的 API 非常簡單,append 和 get。Append 向 DLedger 添加數據,並且添加的數據會對應一個遞增的索引,而 get 可以根據索引去獲得相應的數據。因此 DLedger 是一個 append only 的日誌系統。

2. DLedger 應用場景

 


DLedger 其中一個應用就是在分佈式消息系統中,RocketMQ 4.5 版本發佈後,可以採用 RocketMQ on DLedger 方式進行部署。DLedger commitlog 代替了原來的 commitlog,使得 commitlog 擁有了選舉複製能力,然後通過角色透傳的方式,raft 角色透傳給外部 broker 角色,leader 對應原來的 master,follower 和 candidate 對應原來的 slave。

因此 RocketMQ 的 broker 擁有了自動故障轉移的能力。在一組 broker 中, Master 掛了以後,依靠 DLedger 自動選主能力,會重新選出 leader,然後通過角色透傳變成新的 Master。

 


DLedger 還可以構建高可用的嵌入式 KV 存儲。我們把對一些數據的操作記錄到 DLedger 中,然後根據數據量或者實際需求,恢復到hashmap 或者 rocksdb 中,從而構建一致的、高可用的 KV 存儲系統,應用到元信息管理等場景。

三、DLedger 的優化

1. 性能優化

 


Raft 協議複製過程可以分爲四步,先是發送消息給 leader,leader 除了本地存儲之外,會把消息複製給 follower,然後等待follower 確認,如果得到多數節點確認,該消息就可以被提交,並向客戶端返回發送成功的確認。DLedger 中如何去優化這一複製過程?

(1)異步線程模型

DLedger 採用一個異步線程模型,異步線程模型可以減少等待。在一個系統中,如果阻塞點越少,每個線程處理請求時能減少等待,就能更好的利用 CPU,提高吞吐量和性能。

 


以 DLedger 處理 Append 請求的整個過程來講述 DLedger 異步線程模型。圖中粗箭頭表示 RPC 請求,實現箭頭表示數據流,虛線表示控制流。

首先客戶端發送 Append 請求,由 DLedger 的通信模塊處理,當前 DLedger 默認的通信模塊是利用 Netty 實現的,因此 Netty IO 線程會把請求交給業務線程池中的線程進行處理,然後 IO 線程直接返回,處理下一個請求。業務處理線程處理 Append 請求有三個步驟,首先是把 Append 數據寫入自己日誌中,也就是 pagecache 中。然後生成 Append CompletableFuture ,放入一個 Pending Map 中,由於該日誌還沒有得到多數的確認,所以它是一個判定狀態。第三步喚醒 EnrtyDispatcher 線程,通知該線程去向follower 複製日誌。三步完成以後業務線程就可以去處理下一個 Append 請求,中間幾乎沒有任何等待。

另一方面,複製線程 EntryDispatcher 會向 follower 複製日誌,每一個 follower 都對應一個 EntryDispatcher 線程,該線程去記錄自己對應 follower 的複製位點,每次位點移動後都會去通知 QurumAckChecker 線程,這個線程會根據複製位點的情況,判斷是否一條日誌已經複製到多數節點上,如果已被複制到了多數節點,該日誌就可以被提交,並去完成對應的 Append CompletableFuture ,通知通信模塊向客戶端返回響應。

(2)獨立併發的複製過程

 


在 DLedger 中,leader 向所有 follower 發送日誌也是完全相互獨立和併發的,leader 爲每個 follower 分配一個線程去複製日誌,並記錄相應的複製位點,然後再由一個單獨的異步線程根據位點情況檢測日誌是否被複制到了多數節點上,返回給客戶端響應。

(3)日誌並行複製

 


傳統的線性複製是 leader 向 follower 複製日誌,follower 確認後下一個日誌條目再複製,也就是 leader 要等待 follower 對前一條日誌確認後才能複製下一條日誌。這樣的複製方式保證了順序性,且不會出錯,但吞吐量很低,時延也比較高,因此DLedger設計並實現日誌並行複製的方案,不再需要等待前一個日誌複製完成再複製下一個日誌,只需在 follower 中維護一個按照日誌索引排序請求列表, follower 線程按照索引順序串行處理這些複製請求。而對於並行複製後可能出現數據缺失問題,可以通過少量數據重傳解決。

2. 可靠性優化

(1)DLedger對網絡分區的優化

 


如果出現上圖的網絡分區,n2與集羣中的其他節點發生了網絡隔離,按照 raft 論文實現,n2會一直請求投票,但得不到多數的投票,term 一直增大。一旦網絡恢復後,n2就會去打斷正在正常複製的n1和n3,進行重新選舉。爲了解決這種情況,DLedger 的實現改進了 raft 協議,請求投票過程分成了多個階段,其中有兩個重要階段:WAIT_TO_REVOTE和WAIT_TO_VOTE_NEXT。WAIT_TO_REVOTE是初始狀態,這個狀態請求投票時不會增加 term,WAIT_TO_VOTE_NEXT則會在下一輪請求投票開始前增加 term。對於圖中n2情況,當有效的投票數量沒有達到多數量時。可以將節點狀態設置WAIT_TO_REVOTE,term 就不會增加。通過這個方法,提高了Dledger對網絡分區的容忍性。

(2)DLedger 可靠性測試

DLedger 還有非常高的容錯性。它可以容忍各種各樣原因導致節點無法正常工作,比如:

  • 進程異常崩潰
  • 機器節點異常崩潰(機器斷電,操作系統崩潰)
  • 慢節點(出現 Full GC,OOM 等)
  • 網絡故障,各種各樣的網絡分區

爲了驗證 DLedger 對這些故障的容忍性,除了本地對 DLedger 進行了各種各樣的測試,還利用分佈式系統驗證與故障注入框架 Jepsen 來檢測 DLedger 存在的問題,並驗證系統的可靠性。

 


Jepsen 框架主要是在特定故障下驗證系統是否滿足一致性。Jepsen 驗證系統由 6 個節點組成,一個控制節點(Control Node),五個 DB 節點(DB Node)。控制節點可以通過 SSH 登錄到 DB 節點,通過控制節點的控制,可以在 DB 節點完成分佈式系統的下載,部署,組成一個待測試的集羣。測試開始後,控制節點會創建一組 Worker 進程,每一個 Worker 都有自己的分佈式系統客戶端。Generator 產生每個客戶端執行的操作,客戶端進程將操作應用於待測試的分佈式系統。每個操作的開始和結束以及操作結果記錄在歷史記錄中。同時,一個特殊的 Client 進程 Nemesis 將故障引入系統。測試結束後, Checker 分析歷史記錄是否正確,是否符合一致性。

根據 DLedger 定位,它是一個基於 raft 協議的 commitlog 存儲庫,是一個 append only 的日誌系統,採用 Jepsen 的 Set模型進行測試。Set 模型的測試流程分爲兩個階段。第一階段由不同的客戶端併發地向待測試集羣添加不同的數據,中間會進行故障注入。第二階段,向待測試集羣進行一次最終讀取,獲得讀取的結果集。最後驗證每一個成功添加的元素都在最終結果集中,並且最終的結果集也僅包含企圖添加的元素。

 


上圖是 DLedger 其中一次測試結果,有30個客戶端進程併發地向待測試的 DLedger 集羣添加數據,中間會引入隨機對稱網絡分區,故障引入的間隔時間默認是30s,也就是30s正常運行,30s故障引入,再30s正常運行、30s故障引入,一直循環。整個階段一共持續600s。可以看到最後一共發送了16萬個數據,中間沒有出現數據丟失,lost-count=0,也沒有出現不應該存在的數據,uexpected-count=0,一致性測試通過。

 


上圖展示了該次測試中客戶端對DLedger集羣每一次操作情況,藍色小框表示添加成功,紅色小框表示添加失敗,黃色小框表示不確定是否添加成功(比如多數認證超時),圖中灰色部分表示故障引入的時間段。可以看出一些故障引入時間段造成集羣短暫不可用,一些故障時間段則沒有,這是合理的。因爲是隨機網絡隔離,所以需要看隔離的節點會不會造成集羣重新選舉。但即使造成集羣重新選舉,一段時間後,DLedger集羣也會恢復可用性。

除了測試對稱網絡分區故障,還測試了其他故障下 Dledger 表現情況,包括隨機殺死節點,隨機暫停一些節點的進程模擬慢節點的狀況,以及 bridge、partition-majorities-ring 等複雜的非對稱網絡分區。在這些故障下,DLedger 都保證了一致性,驗證了 DLedger 有很好可靠性。

四、DLedger 未來發展

DLedger 接下來的計劃包括:

  • Leader 節點優先選擇
  • RocketMQ on DLedger 的Jepsen 測試
  • 運行時成員變更
  • 增加觀察者(只參與複製,不參與投票)
  • 構建高可用的K/V存儲
  • ……

DLedger 現在是在 OpenMessaging 下的一個項目,歡迎社區的同學一起加入,來構建高可用高性能的 commitlog 存儲庫。

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