SOFAJRaft 是一個基於 Raft 一致性算法的生產級高性能 Java 實現,支持 MULTI-RAFT-GROUP,適用於高負載低延遲的場景。算法
SOFAJRaft 存儲模塊分爲:緩存
本文將圍繞日誌存儲,元信息存儲以及快照存儲等方面剖析 SOFAJRaft 存儲模塊原理,闡述如何解決 Raft 協議存儲問題以及存儲模塊實現:安全
Log 存儲,記錄 Raft 配置變動和用戶提交任務的日誌,把日誌從 Leader 複製到其餘節點上面。網絡
LogStorage 日誌存儲實現,定義 Raft 分組節點 Node 的 Log 存儲模塊核心 API 接口包括:數據結構
Log Index 提交到 Raft Group 中的任務序列化爲日誌存儲,每條日誌一個編號,在整個 Raft Group 內單調遞增並複製到每一個 Raft 節點。LogStorage 日誌存儲實現接口定義入口:併發
com.alipay.sofa.jraft.storage.LogStorage
Log Structured Merge Tree 簡稱 LSM ,把一顆大樹拆分紅 N 棵小樹,數據首先寫入內存,內存裏構建一顆有序小樹,隨着小樹愈來愈大,內存的小樹 Flush 到磁盤,磁盤中的樹按期作合併操做合併成一棵大樹以優化讀性能,經過把磁盤的隨機寫轉化爲順序寫提升寫性能,RocksDB 就是基於 LSM-Tree 數據結構使用 C++ 編寫的嵌入式 KV 存儲引擎,其鍵值均容許使用二進制流。RocksDB 按順序組織全部數據,通用操做包括 get(key), put(key), delete(Key) 以及 newIterator()。RocksDB 有三種基本的數據結構:memtable,sstfile 以及 logfile。memtable 是一種內存數據結構--全部寫入請求都會進入 memtable,而後選擇性進入 logfile。logfile 是一種有序寫存儲結構,當 memtable 被填滿的時候被刷到 sstfile 文件並存儲起來,而後相關的 logfile 在以後被安全地刪除。sstfile 內的數據都是排序好的,以便於根據 key 快速搜索。app
LogStorage 默認實現 RocksDBLogStorage 是基於 RocksDB 存儲日誌,初始化日誌存儲 StorageFactory 根據 Raft節點日誌存儲路徑和 Raft 內部實現是否調用 fsync 配置默認建立 RocksDBLogStorage 日誌存儲。基於 RocksDB 存儲實現 RocksDBLogStorage 核心操做包括:性能
RocksDBLogStorage 基於 RocksDB 存儲日誌實現核心入口:優化
com.alipay.sofa.jraft.storage.RocksDBLogStorage
日誌管理器 LogManager 負責調用 Log 日誌存儲 LogStorage,對 LogStorage 調用進行緩存管理、批量提交、檢查優化。Raft 分組節點 Node 初始化/啓動時初始化日誌存儲 StorageFactory 構建日誌管理器 LogManager,基於日誌存儲 LogStorage、配置管理器 ConfigurationManager、有限狀態機調用者 FSMCaller、節點性能監控 NodeMetrics 等 LogManagerOptions 配置選項實例化 LogManager。根據 Raft 節點 Disruptor Buffer 大小配置生成穩定狀態回調 StableClosure 事件 Disruptor 隊列,設置穩定狀態回調 StableClosure 事件處理器 StableClosureEventHandler 處理隊列事件,其中 StableClosureEventHandler 處理器事件觸發的時候判斷任務回調 StableClosure 的 Log Entries 是否爲空,若是任務回調的 Log Entries 爲非空需積攢日誌條目批量 Flush,空則檢查 StableClosureEvent 事件類型而且調用底層存儲 LogStorage#appendEntries(entries) 批量提交日誌寫入 RocksDB,當事件類型爲SHUTDOWN、RESET、TRUNCATE_PREFIX、TRUNCATE_SUFFIX、LAST_LOG_ID 時調用底層日誌存儲 LogStorage 進行指定事件回調 ResetClosure、TruncatePrefixClosure、TruncateSuffixClosure、LastLogIdClosure 處理。ui
當 Client 向 SOFAJRaft 發送命令以後,Raft 分組節點 Node 的日誌管理器 LogManager 首先將命令以 Log 的形式存儲到本地,調用 appendEntries(entries, done) 方法檢查 Node 節點當前爲 Leader 而且 Entries 來源於用戶未知分配到的正確日誌索引時須要分配索引給添加的日誌 Entries ,而當前爲 Follower 時而且 Entries 來源於 Leader 必須檢查以及解決本地日誌和 Entries 之間的衝突。接着遍歷日誌條目 Log Entries 檢查類型是否爲配置變動,配置管理器 ConfigurationManager 緩存配置變動 Entry,將現有日誌條目 Entries 添加到 logsInMemory 進行緩存,穩定狀態回調 StableClosure 設置須要存儲的日誌,發佈 OTHER 類型事件到穩定狀態回調 StableClosure 事件隊列,觸發穩定狀態回調 StableClosure 事件處理器 StableClosureEventHandler 處理該事件,處理器獲取任務回調的 Log Entries 把日誌條目積累到內存中以便後續統一批量 Flush,經過 appendToStorage(toAppend) 操做調用底層LogStorage 存儲日誌 Entries。同時 Replicator 把此條 Log 複製給其餘的 Node 實現併發的日誌複製,當 Node 接收集羣中半數以上的 Node 返回的「複製成功」的響應將這條 Log 以及以前的 Log 有序的發送至狀態機裏面執行。
LogManager 調用日誌存儲 LogStorage 實現邏輯:
Metadata 存儲即元信息存儲,用來存儲記錄 Raft 實現的內部狀態,譬如當前任期 Term、投票給哪一個 PeerId 節點等信息。
RaftMetaStorage 元信息存儲實現,定義 Raft 元數據的 Metadata 存儲模塊核心 API 接口包括:
Raft 內部狀態任期 Term 是在整個 Raft Group 裏單調遞增的 long 數字,用來表示一輪投票的編號,其中成功選舉出來的 Leader 對應的 Term 稱爲 Leader Term,Leader 沒有發生變動期間提交的日誌都有相同的 Term 編號。PeerId 表示 Raft 協議的參與者(Leader/Follower/Candidate etc.), 由三元素組成: ip:port:index,其中 ip 是節點的 IP, port 是端口, index 表示同一個端口的序列號。RaftMetaStorage 元信息存儲實現接口定義入口:
com.alipay.sofa.jraft.storage.RaftMetaStorage
Protocol Buffers 是一種輕便高效的結構化數據存儲格式,用於結構化數據串行化或者說序列化,適合作數據存儲或 RPC 數據交換格式,用於通信協議、數據存儲等領域的語言無關、平臺無關、可擴展的序列化結構數據格式。用戶在 .proto 文件定義 Protocol Buffer 的 Message 類型指定須要序列化的數據結構,每個 Message 都是一個小的信息邏輯單元包含一系列的鍵值對,每種類型的 Message 涵蓋一個或者多個惟一編碼字段,每一個字段由名稱和值類型組成,容許 Message 定義可選字段 Optional Fields、必須字段 Required Fields、可重複字段 Repeated Fields。
RaftMetaStorage 默認實現 LocalRaftMetaStorage 是基於 ProtoBuf Message 本地存儲 Raft 元數據,初始化元信息存儲 StorageFactory 根據 Raft 元信息存儲路徑、 Raft 內部配置以及 Node 節點監控默認建立 LocalRaftMetaStorage 元信息存儲。基於 ProtoBuf 存儲實現 LocalRaftMetaStorage 主要操做包括:
LocalRaftMetaStorage 基於 ProtoBuf 本地存儲 Raft 元信息實現入口:
com.alipay.sofa.jraft.storage.impl.LocalRaftMetaStorage
當 Raft 節點 Node 重啓時,內存中狀態機的狀態數據丟失,觸發啓動過程從新存放日誌存儲 LogStorage 的全部日誌重建整個狀態機實例,此種場景會致使兩個問題:
所以經過引入 Snapshot 機制來解決此三個問題,所謂快照 Snapshot 即對數據當前值的記錄,是爲當前狀態機的最新狀態構建"鏡像"單獨保存,保存成功刪除此時刻以前的日誌減小日誌存儲佔用;啓動的時候直接加載最新的 Snapshot 鏡像,而後重放在此以後的日誌便可,若是 Snapshot 間隔合理,整個重放到狀態機過程較快,加速啓動過程。最後新節點的加入先從 Leader 拷貝最新的 Snapshot 安裝到本地狀態機,而後只要拷貝後續的日誌便可,可以快速跟上整個 Raft Group 的進度。Leader 生成快照有幾個做用:
Snapshot 存儲,用於存儲用戶的狀態機 Snapshot 及元信息:
SnapshotStorage 快照存儲實現,定義 Raft 狀態機的 Snapshot 存儲模塊核心 API 接口包括:
SnapshotStorage 默認實現 LocalSnapshotStorage 是基於本地文件存儲 Raft 狀態機鏡像,初始化元快照存儲 StorageFactory 根據 Raft 鏡像快照存儲路徑和 Raft 配置信息默認建立 LocalSnapshotStorage 快照存儲。基於本地文件存儲實現 LocalSnapshotStorage 主要方法包括:
快照執行器 SnapshotExecutor 負責 Raft 狀態機 Snapshot 存儲、Leader 遠程安裝快照、複製鏡像 Snapshot 文件,包括兩大核心操做:狀態機快照 doSnapshot(done) 和安裝快照 installSnapshot(request, response, done)。StateMachine 快照 doSnapshot(done) 獲取基於臨時鏡像 temp 文件路徑的 Snapshot 存儲快照編寫器 LocalSnapshotWriter,加載 __raft_snapshot_meta 快照元數據文件初始化編寫器;構建保存鏡像回調SaveSnapshotDone 提供 FSMCaller 調用 StateMachine 的狀態轉換髮布 SNAPSHOT_SAVE 類型任務事件到 Disruptor 隊列,經過 Ring Buffer 方式觸發申請任務處理器 ApplyTaskHandler 運行快照保存任務,調用 onSnapshotSave() 方法存儲各類類型狀態機快照。遠程安裝快照 installSnapshot(request, response, done) 按照安裝鏡像請求響應以及快照原信息建立而且註冊快照下載做業 DownloadingSnapshot,加載快照下載 DownloadingSnapshot 獲取當前快照拷貝器的閱讀器 SnapshotReader,構建安裝鏡像回調 InstallSnapshotDone 分配 FSMCaller 調用 StateMachine 的狀態轉換髮布 SNAPSHOT_LOAD 類型任務事件到 Disruptor 隊列,也是經過 Ring Buffer 觸發申請任務處理器 ApplyTaskHandler 執行快照安裝任務,調用 onSnapshotLoad() 操做加載各類類型狀態機快照。
SnapshotExecutor 狀態機快照和遠程安裝鏡像實現邏輯:
本文從 Log 日誌存儲 LogStorage、Meta 元信息存儲 RaftMetaStorage 以及 Snapshot 快照存儲 SnapshotStorage 三個方面詳述 SOFAJRaft 存儲模塊實現細節,直觀刻畫 SOFAJRaft Server 節點 Node 之間存儲日誌、Raft 配置和鏡像流程。
原文連接 本文爲雲棲社區原創內容,未經容許不得轉載。