DLedger——基於 Raft 的 Commitlog 存儲 Library

DLedger——基於 Raft 的 Commitlog 存儲 Library

https://github.com/openmessaging/openmessaging-storage-dledger

故事的起源

自分佈式系統誕生以來,容災和一致性,一直是經常被討論的話題。
Master-Slave 架構是最容易被想到的設計,簡單而易於實現,被早期大部分分佈式系統採用,包括 RocketMQ 早期的高可用架構,其略顯粗陋的一致性保證、缺少自動 Failover 等,並不能滿足需求。
後來,Hadoop 迅猛發展改變了這一面貌。Hadoop 生態裏面的 Zookeeper 組件,可以作爲一個高可用的鎖而存在,由此引發了大量系統通過 Zookeeper 選主,然後主備複製日誌,來達到高可用和一致性的目的。Hadoop 自身 NameNode 組件 的高可用機制便是這一典型實現。

基於 ZooKeeper 的設計,通過一些複雜的寫入 fence,基本可以滿足需求。但 Zookeeper 自身的複雜性,加重了整個設計,在具體實施和運維時,不僅增加資源成本,還累積了系統風險,讓維護人員叫苦不堪。

再後來,Raft 論文的出現,再一次改變了局面。其簡潔易懂的設計,沒有任何外部依賴,就可以輕鬆搞定一個高可靠、高可用、強一致的數據複製系統,讓廣大分佈式系統研發人員如獲至寶。

本文的主角 DLedger 就是這樣的一個實踐者。

DLedger 的定位

DLedger 定位是一個工業級的 Java Library,可以友好地嵌入各類 Java 系統中,滿足其高可用、高可靠、強一致的需求。
和這一定位比較接近的是 Ratis
Ratis 是一個典型的"日誌 + 狀態機"的實現,雖然其狀態機可以自定義,卻仍然不滿足消息領域的需求。 在消息領域,如果根據日誌再去構建“消息狀態機”,就會產生 Double IO 的問題,造成極大的資源浪費,因此,在消息領域,是不需要狀態機的,日誌和消息應該是合二爲一。
相比於 Ratis,DLedger 只提供日誌的實現,只擁有日誌寫入和讀出的接口,且對順序讀出和隨機讀出做了優化,充分適應消息系統消峯填谷的需求。
DLedger 的純粹日誌寫入和讀出,使其精簡而健壯,總代碼不超過4000行,測試覆蓋率高達70%。而且這種原子化的設計,使其不僅可以充分適應消息系統,也可以基於這些日誌去構建自己的狀態機,從而適應更廣泛的場景。
綜上所述,DLedger 是一個基於 Raft 實現的、高可靠、高可用、強一致的 Commitlog 存儲 Library。

DLedger 的實現

DLedger 的實現大體可以分爲以下兩個部分: 1.選舉 Leader 2.複製日誌 其整體架構如下圖 DLedger Architect

本文不展開討論實現的細節,詳情可以參考論文[1]。有興趣的也可以直接參看源碼,項目總體不超過4000行代碼,簡潔易讀。

DLedger 的應用案例

在 Apache RocketMQ 中,DLedger 不僅被直接用來當做消息存儲,也被用來實現一個嵌入式的 KV 系統,以存儲元數據信息。

更多的接入案例,敬請期待。

案例1 DLedger 作爲 RocketMQ 的消息存儲

架構如下圖所示: DLedger Commitlog其中:

  1. DLedgerCommitlog 用來代替現有的 Commitlog 存儲實際消息內容,它通過包裝一個 DLedgerServer 來實現複製;

  2. 依靠 DLedger 的直接存取日誌的特點,消費消息時,直接從 DLedger 讀取日誌內容作爲消息返回給客戶端;

  3. 依靠 DLedger 的 Raft 選舉功能,通過 RoleChangeHandler 把角色變更透傳給 RocketMQ 的Broker,從而達到主備自動切換的目標

案例2 利用 DLedger 實現一個高可用的嵌入式 KV 存儲

架構圖如下所示: DLedger KV

其中:

  1. DLedger 用來存儲 KV 的增刪改日誌;

  2. 通過將日誌一條條 Apply 到本地 Map,比如 HashMap 或者 第三方 的 RocksDB等

整個系統的高可用、高可靠、強一致通過 DLedger 來實現。

社區發展計劃

目前 DLedger 已經成爲 OpenMessaging 中存儲標準的默認實現。DLedger 會維持自身定位不變,作爲一個精簡的 Commitlog 存儲 Library,後續主要是做性能優化和一些必要的特性補充,比如支持手工配置 Leader 節點,支持蛻化成 Master-Slave 架構等。 基於 DLedger 的開發,也可以作爲獨立項目進行孵化,比如 OpenMessaging KV
歡迎社區的朋友們一起來共建。

優先 Leader

目標

優先選舉某個節點爲 Leader,以控制負載。 假設某個組中包含 A B C 三個節點,指定 A 爲優先節點,則期望以下自動行爲:

  1. 如果 A 節點在線,且與其它節點的日誌高度差別不大時,優先選舉 A 爲 Leader

  2. 如果 A 節點掉線後又上線,且日誌高度追到與當前 Leader 較爲接近時,當前 Leader 節點要嘗試讓出 Leader 角色給 A 節點

方案

優先Leader的整體方案依賴搶主(Take Leadership)和主轉讓(Leadership Transfer)兩個子操作。

搶主 Take Leadership

Take Leadership 是指當前節點在想要主動成爲Leader角色時,執行的搶主操作。

  1. 如果當前節點是Follower, 將term+1,轉爲Candidate,進入搶主狀態。如果已經是Candidate,也將term+1,進入搶主狀態,具備如下特性:

  2. 在日誌高度一樣時,不會爲同一個term的其他Candidate投票

  3. 將會以更小的重試間隔(voteInterval)進行選主。

  4. 狀態只維持一個term的選舉。

  5. 其他操作同普通的選主過程

搶主操作並不保證改節點一定能成爲主。

主轉讓 Leadership Transfer

指定節點轉讓 Leader 角色是優先 Leader 的一個依賴功能,同時也可以作爲獨立的功能存在,實現手動的負載均衡。下面是描述其實現方案:

  1. 首先 Leader 節點(假設爲A節點)提供接口,接收將當前的Leadership 轉讓給 B 節點。

  2. A 節點收到轉讓命令之後,爲了避免過長時間的不可用,先檢查B節點的落後的數據量 是否小於 配置的閾值(maxLeadershipTransferWaitIndex),超了則返回轉換失敗,結束任務。

  3. 如果B節點進度落後不多,則標記 A 節點狀態爲 "切換",拒絕接收新的數據寫入。

  4. A 節點檢查B節點的日誌數據是否已經是最新,如果還缺數據,則 A 節點繼續往 B 節點推送數據。

  5. 當 B 節點的數據同步完成之後,A 節點往 B 節點發送命令,讓其進行 Take Leadership 操作,爭搶主節點。

Preferred Leader

在 Preferred 節點上,選舉的過程類似“搶主”的邏輯,但是不提升Term,避免打斷集羣。只有收到明確的“搶主”命令時才提升Term。 在 Leader 上,輪詢檢查,優先節點是否爲主,如果不是主並且該節點健康,就發起 Leadership Transfer 操作, 嘗試將主節點轉讓給該優先節點。

快速開始

條件

  • 64位JDK 1.8+;

  • Maven 3.2.x.

建立

mvn clean install -DskipTests

運行命令行

## Get Command Usage
java -jar target/DLedger.jar

## Start DLedger Server
nohup java -jar target/DLedger.jar server &

## Append Data to DLedger
java -jar target/DLedger.jar append -d "Hello World"

## Get Data from DLedger
java -jar target/DLedger.jar get -i 0


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