Raft算法之日志壓縮
上一篇文章:Raft算法之成員關系變化
最后的一部分是關於服務器日志壓縮的,因為隨着運行時間的增增長,日志信息也會變得越來越多,占有更多的空間。因此Raft采取了日志壓縮的方法解決該問題,即將當前整個系統狀態寫入穩定存儲的快照,然后該時間點之前的日志就可以丟棄掉,從而釋放存儲空間。
1 快照結構
從圖中可見,快照包括以下幾個部分內容:
- lastIncludedIndex: 表明快照中最后一條日志的索引值。也就是說日志一直壓縮到該索引值的位置。該值以前連續若干個索引值的日志被壓縮為快照,而該值以后的日志則不在快照中。
- lastIncludedTerm:表明快照中最后一條日志所在的任期值。
- state machine state:復制狀態機的當前狀態。
集群中每一個服務器都可以獨立地進行拍攝快照(只對已提交的日志進行快照的拍攝),其中lastIncludedIndex
與lastIncludedTerm
值的存在時為了通過之前講到的在日志復制中需要做的一致性檢查。當服務器完成了該快照的寫入之后,就可以將從快照中最后一條日志一直到先前所有的日志刪除。
2 快照的發送
正常情況下,Leader
的日志將會與Follower
保持一致,但並不是所有情況都處於正常情況下,有時候可能因為Follower
的反應緩慢或崩潰造成與Leader
的日志不一致。所以有時候需要Leader
將快照信息發送給Follower
。快照信息是通過一個稱為InstallSnapshot
的RPC消息發送的,該消息的結構如下:
InstallSnapshot RPC | 由Leader調用並按順序發送打包的快照到Follower。 |
---|---|
參數: | |
term | Leader的任期 |
leaderId | 用以Follower可以重定向客戶端的請求到Leader |
lastIncludedIndex | 快照將替換直到該索引的所有日志條目 |
lastIncludedTerm | 快照中最后一條日志所在的任期值 |
offset | 快照文件的偏移量,簡單來說就是該快照代表多少數量的連續個日志實體 |
data[] | 從offset開始,快照內的數據數組 |
done | 如果這是最后一個快照則為true,說明該快照之后的日志暫時不需要拍攝快照 |
快照的發送會出現以下幾種情況:
Follower
的日志信息不包括快照中的日志信息,即缺少日志。Follower
的日志信息與快照中的日志信息發生沖突。Follower
的日志信息要多於快照中的日志信息。
至於前兩種情況,Follower
采取直接使用快照內容替代掉自己的日志。
Follower
只含有部分快照中的日志信息,那么直接刪掉然后使用快照取代。Follower
具有更多的日志信息的情況下,即Follower
含有大於接收到的快照中的最后一條日志信息的索引的日志信息。那么直接使用快照代替快照中所包含的日志信息,至於快照之后的日志信息仍然保留。
2.1 疑問
為什么不像日志一樣僅由Leader
拍攝快照然后發送給Follower
,而是允許每一個服務器獨立生成快照信息呢?
很簡單的答案,為了減少帶寬使用,以及資源的浪費。因為正常情況下Follower
具有生成快照的所有信息,在自己本地直接生成快照所需要消耗的資源要遠遠小於通過網絡發送所需要的資源。另外也是降低Leader
設計的復雜。因為如果僅由Leader
生成快照的話,Leader
則需要在向Follower
發送日志的同時,還要兼顧快照的發送。
2.2 存在的問題
- 還有另外兩個問題會影響每個快照。首先,服務器必須決定何時進行快照。如果服務器過於頻繁地進行快照,則會浪費磁盤帶寬和能量。如果快照太不頻繁,則有耗盡其存儲容量的風險,並且會增加重新啟動期間加載日志所需的時間。一種簡單的策略是在日志達到固定大小(以字節為單位)時拍攝快照。如果此大小設置為明顯大於快照的預期大小,則用於快照的磁盤帶寬開銷將很小。
- 第二個問題是寫快照可能要花費大量時間,拍攝快照會延遲正常操作。解決方案是使用寫時復制技術,以便可以接受新的更新而不會影響快照的寫入。例如,使用功能性數據結構構建的狀態機自然支持這一點。或者,可以使用操作系統的寫時復制支持(例如,Linux上的fork)來創建整個狀態機的內存快照。