您好,登錄后才能下訂單哦!
今天就跟大家聊聊有關怎么進行SOFAJRaft 實現原理的分析,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結了以下內容,希望大家根據這篇文章可以有所收獲。
從 Log 日志存儲 LogStorage、Meta 元信息存儲 RaftMetaStorage 以及 Snapshot 快照存儲 SnapshotStorage 三個方面詳述 SOFAJRaft 存儲模塊實現細節,直觀刻畫 SOFAJRaft Server 節點 Node 之間存儲日志、Raft 配置和鏡像流程。
SOFAStack
Scalable Open Financial Architecture Stack
是螞蟻金服自主研發的金融級分布式架構,包含了構建金融級云原生架構所需的各個組件,是在金融場景里錘煉出來的最佳實踐。SOFAJRaft 是一個基于 Raft 一致性算法的生產級高性能 Java 實現,支持 MULTI-RAFT-GROUP,適用于高負載低延遲的場景。
SOFAJRaft 是一個基于 Raft 一致性算法的生產級高性能 Java 實現,支持 MULTI-RAFT-GROUP,適用于高負載低延遲的場景。
SOFAJRaft 存儲模塊分為:
Log 存儲記錄 Raft 配置變更和用戶提交任務日志;
Meta 存儲即元信息存儲記錄 Raft 實現的內部狀態;
Snapshot 存儲用于存放用戶的狀態機 Snapshot 及元信息。
本文將圍繞日志存儲,元信息存儲以及快照存儲等方面剖析 SOFAJRaft 存儲模塊原理,闡述如何解決 Raft 協議存儲問題以及存儲模塊實現:
Raft 配置變更和用戶提交任務日志如何存儲?如何調用管理日志存儲?
SOFAJRaft Server 節點 Node 是如何存儲 Raft 內部配置?
Raft 狀態機快照 Snapshot 機制如何實現?如何存儲安裝鏡像?
cdn.nlark.com/yuque/0/2019/png/156670/1556492476096-9300c652-29e2-4698-b5ef-435c294e00c6.png">
Log 存儲,記錄 Raft 配置變更和用戶提交任務的日志,把日志從 Leader 復制到其他節點上面。
LogStorage 是日志存儲實現,默認實現基于 RocksDB 存儲,通過 LogStorage 接口擴展自定義日志存儲實現;
LogManager 負責調用底層日志存儲 LogStorage,針對日志存儲調用進行緩存、批量提交、必要的檢查和優化。
LogStorage 日志存儲實現,定義 Raft 分組節點 Node 的 Log 存儲模塊核心 API 接口包括:
返回日志里的首/末個日志索引;
按照日志索引獲取 Log Entry 及其任期;
把單個/批量 Log Entry 添加到日志存儲;
從 Log 存儲頭部/末尾刪除日志;
刪除所有現有日志,重置下任日志索引。
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 快速搜索。
LogStorage 默認實現 RocksDBLogStorage 是基于 RocksDB 存儲日志,初始化日志存儲 StorageFactory 根據 Raft節點日志存儲路徑和 Raft 內部實現是否調用 fsync 配置默認創建 RocksDBLogStorage 日志存儲。基于 RocksDB 存儲實現 RocksDBLogStorage 核心操作包括:
init():創建 RocksDB 配置選項調用 RocksDB#open() 方法構建 RocksDB 實例,添加 default 默認列族及其配置選項獲取列族處理器,通過 newIterator() 生成 RocksDB 迭代器遍歷 KeyValue 數據檢查 Value 類型加載 Raft 配置變更到配置管理器 ConfigurationManager。RocksDB 引入列族 ColumnFamily 概念,所謂列族是指一系列 KeyValue 組成的數據集,RocksDB 讀寫操作需要指定列族,創建 RocksDB 默認構建命名為default 的列族。
shutdown():首先關閉列族處理器以及 RocksDB 實例,其次遍歷列族配置選項執行關閉操作,接著關閉RocksDB 配置選項,最后清除強引用以達到 Help GC 垃圾回收 RocksDB 實例及其配置選項對象。
getFirstLogIndex():基于處理器 defaultHandle 和讀選項 totalOrderReadOptions 方法構建 RocksDB 迭代器 RocksIterator,檢查是否加載過日志里第一個日志索引,未加載需調用 seekToFirst() 方法獲取緩存 RocksDB 存儲日志數據的第一個日志索引。
getLastLogIndex():基于處理器 defaultHandle 和讀選項 totalOrderReadOptions 構建 RocksDB 迭代器 RocksIterator,調用 seekToLast() 方法返回 RocksDB 存儲日志記錄的最后一個日志索引。
getEntry(index):基于處理器 defaultHandle 和指定日志索引調用 RocksDB#get() 操作返回 RocksDB 索引位置日志 LogEntry。
getTerm(index):基于處理器 defaultHandle 和指定日志索引調用 RocksDB#get() 操作獲取 RocksDB 索引位置日志并且返回其 LogEntry 的任期。
appendEntry(entry):檢查日志 LogEntry 類型是否為配置變更,配置變更類型調用 RocksDB#write() 方法執行批量寫入,用戶提交任務的日志基于處理器 defaultHandle 和 LogEntry 對象調用 RocksDB#put() 方法存儲。
appendEntries(entries):調用 RocksDB#write() 方法把 Raft 配置變更或者用戶提交任務的日志同步刷盤批量寫入 RocksDB 存儲,通過 Batch Write 手段合并 IO 寫入請求減少方法調用和上下文切換。
truncatePrefix(firstIndexKept):獲取第一個日志索引,后臺啟動一個線程基于默認處理器 defaultHandle 和配置處理器 confHandle 執行 RocksDB#deleteRange() 操作刪除從 Log 頭部以第一個日志索引到指定索引位置范圍的 RocksDB 日志數據。
truncateSuffix(lastIndexKept):獲取最后一個日志索引,基于默認處理器 defaultHandle 和配置處理器 confHandle 執行 RocksDB#deleteRange() 操作清理從 Log 末尾以指定索引位置到最后一個索引范疇的 RocksDB 未提交日志。
reset(nextLogIndex):獲取 nextLogIndex 索引對應的 LogEntry,執行 RocksDB#close() 方法關閉 RocksDB實例,調用 RocksDB#destroyDB() 操作銷毀 RocksDB 實例清理 RocksDB 所有數據,重新初始化加載 RocksDB 實例并且重置下一個日志索引位置。
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 處理。
當 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 元信息的 PeerId 節點投票。
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 主要操作包括:
init():獲取 Raft 元信息存儲配置 RaftMetaStorageOptions 節點 Node,讀取命名為 raft_meta 的 ProtoBufFile 文件加載 StablePBMeta 消息,根據 StablePBMeta ProtoBuf 元數據緩存 Raft 當前任期 Term 和 PeerId 節點投票信息。
shutdown():獲取內存里 Raft 當前任期 Term 和 PeerId 節點投票構建 StablePBMeta 消息,按照 Raft 內部是否同步元數據配置寫入 ProtoBufFile 文件。
setTerm(term):檢查 LocalRaftMetaStorage 初始化狀態,緩存設置的當前任期 Term,按照 Raft 是否同步元數據配置把當前任期 Term 作為 ProtoBuf 消息保存到 ProtoBufFile 文件。
getTerm():檢查 LocalRaftMetaStorage 初始化狀態,返回緩存的當前任期 Term。
setVotedFor(peerId):檢查 LocalRaftMetaStorage 初始化狀態,緩存投票的 PeerId 節點,按照 Raft 是否同步元數據配置把投票 PeerId 節點作為 ProtoBuf 消息保存到 ProtoBufFile 文件。
getVotedFor():檢查 LocalRaftMetaStorage 初始化狀態,返回緩存的投票 PeerId 節點。
LocalRaftMetaStorage 基于 ProtoBuf 本地存儲 Raft 元信息實現入口:
com.alipay.sofa.jraft.storage.impl.LocalRaftMetaStorage
當 Raft 節點 Node 重啟時,內存中狀態機的狀態數據丟失,觸發啟動過程重新存放日志存儲 LogStorage 的所有日志重建整個狀態機實例,此種場景會導致兩個問題:
如果任務提交比較頻繁,例如消息中間件場景導致整個重建過程很長啟動緩慢;
如果日志非常多并且節點需要存儲所有的日志,對存儲來說是資源占用不可持續;
如果增加 Node 節點,新節點需要從 Leader 獲取所有的日志重新存放至狀態機,對于 Leader 和網絡帶寬都是不小的負擔。
因此通過引入 Snapshot 機制來解決此三個問題,所謂快照 Snapshot 即對數據當前值的記錄,是為當前狀態機的最新狀態構建"鏡像"單獨保存,保存成功刪除此時刻之前的日志減少日志存儲占用;啟動的時候直接加載最新的 Snapshot 鏡像,然后重放在此之后的日志即可,如果 Snapshot 間隔合理,整個重放到狀態機過程較快,加速啟動過程。最后新節點的加入先從 Leader 拷貝最新的 Snapshot 安裝到本地狀態機,然后只要拷貝后續的日志即可,能夠快速跟上整個 Raft Group 的進度。Leader 生成快照有幾個作用:
當有新的節點 Node 加入集群不用只靠日志復制、回放機制和 Leader 保持數據一致,通過安裝 Leader 的快照方式跳過早期大量日志的回放;
Leader 用快照替代 Log 復制減少網絡端的數據量;
用快照替代早期的 Log 節省存儲占用空間。
Snapshot 存儲,用于存儲用戶的狀態機 Snapshot 及元信息:
SnapshotStorage 用于 Snapshot 存儲實現;
SnapshotExecutor 用于管理 Snapshot 存儲、遠程安裝、復制。
SnapshotStorage 快照存儲實現,定義 Raft 狀態機的 Snapshot 存儲模塊核心 API 接口包括:
設置 filterBeforeCopyRemote 設置為 true 復制到遠程之前過濾數據;
創建快照編寫器;
打開快照閱讀器;
從遠程 Uri 復制數據;
啟動從遠程 Uri 復制數據的復制任務;
配置 SnapshotThrottle,SnapshotThrottle 用于重盤讀/寫場景限流的,比如磁盤讀寫、網絡帶寬。
SnapshotStorage 默認實現 LocalSnapshotStorage 是基于本地文件存儲 Raft 狀態機鏡像,初始化元快照存儲 StorageFactory 根據 Raft 鏡像快照存儲路徑和 Raft 配置信息默認創建 LocalSnapshotStorage 快照存儲。基于本地文件存儲實現 LocalSnapshotStorage 主要方法包括:
init():刪除文件命名為 temp 的臨時鏡像 Snapshot,銷毀文件前綴為 snapshot_ 的舊快照 Snapshot,獲取快照最后一個索引 lastSnapshotIndex。
close():按照快照最后一個索引 lastSnapshotIndex 和鏡像編寫器 LocalSnapshotWriter 快照索引重命名臨時鏡像 Snapshot 文件,銷毀編寫器 LocalSnapshotWriter 存儲路徑快照。
create():銷毀文件命名為 temp 的臨時快照 Snapshot,基于臨時鏡像存儲路徑創建初始化快照編寫器 LocalSnapshotWriter,加載文件命名為 __raft_snapshot_meta 的 Raft 快照元數據至內存。
open():根據快照最后一個索引 lastSnapshotIndex 獲取文件前綴為 snapshot_ 快照存儲路徑,基于快照存儲路徑創建初始化快照閱讀器 LocalSnapshotReader,加載文件命名為 __raft_snapshot_meta 的 Raft 鏡像元數據至內存。
startToCopyFrom(uri, opts):創建初始化狀態機快照復制器 LocalSnapshotCopier,生成遠程文件復制器 RemoteFileCopier,基于遠程服務地址 Endpoint 獲取 Raft 客戶端 RPC 服務連接指定 Uri,啟動后臺線程復制 Snapshot 鏡像數據,加載 Raft 快照元數據獲取遠程快照 Snapshot 鏡像文件,讀取遠程指定快照存儲路徑數據拷貝到 BoltSession,快照復制器 LocalSnapshotCopier 同步 Raft 快照元數據。
快照執行器 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 狀態機快照和遠程安裝鏡像實現邏輯:
看完上述內容,你們對怎么進行SOFAJRaft 實現原理的分析有進一步的了解嗎?如果還想了解更多知識或者相關內容,請關注億速云行業資訊頻道,感謝大家的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。