Braft的日志存儲引擎實現(xiàn)分析
1.架構(gòu)設(shè)計
1.1 函數(shù)接口說明
日志存儲引擎是用于存儲raft lib產(chǎn)生的日志菌瘪。提供的接口如下:
class LogStorage {
public:
virtual ~LogStorage() {}
// init logstorage, check consistency and integrity
virtual int init(ConfigurationManager* configuration_manager) = 0;
// first log index in log
virtual int64_t first_log_index() = 0;
// last log index in log
virtual int64_t last_log_index() = 0;
// get logentry by index
virtual LogEntry* get_entry(const int64_t index) = 0;
// get logentry's term by index
virtual int64_t get_term(const int64_t index) = 0;
// append entries to log
virtual int append_entry(const LogEntry* entry) = 0;
// append entries to log, return append success number
virtual int append_entries(const std::vector<LogEntry*>& entries) = 0;
// delete logs from storage's head, [first_log_index, first_index_kept) will be discarded
virtual int truncate_prefix(const int64_t first_index_kept) = 0;
// delete uncommitted logs from storage's tail, (last_index_kept, last_log_index] will be discarded
virtual int truncate_suffix(const int64_t last_index_kept) = 0;
// Drop all the existing logs and reset next log index to |next_log_index|.
// This function is called after installing snapshot from leader
virtual int reset(const int64_t next_log_index) = 0;
// Create an instance of this kind of LogStorage with the parameters encoded
// in |uri|
// Return the address referenced to the instance on success, NULL otherwise.
virtual LogStorage* new_instance(const std::string& uri) const = 0;
static LogStorage* create(const std::string& uri);
};
LogStorage只是一個抽象類衔彻,只定義了函數(shù)接口方援。具體的日志操作由SegmentLogStorage實現(xiàn)薛耻。
1.2 存儲引擎的數(shù)據(jù)組織
SegmentLogStorage實現(xiàn)了LogStorage的全部接口床玻。其數(shù)據(jù)組織格式如下:
- segment名字為first_raft_index-last_raft_index翼虫,表示該segment的raft index范圍屑柔。
- 只有最后一個segment可讀寫,其文件名為log_inprogress_first_raft_index珍剑,其他segment只讀掸宛。
- segment文件對應(yīng)的index entry,Segment文件初始化時構(gòu)造出來招拙,存儲在內(nèi)存中唧瘾。不會持久化到磁盤。因此追加一次Log Entry只會引起一次磁盤操作别凤。
2.核心流程實現(xiàn)
2.1 存儲引擎的接口函數(shù)
2.2 存儲引擎的初始化
存儲引擎的初始化操作主要檢查文件信息饰序,將segment的索引信息加載到內(nèi)存,為讀寫操作做準備规哪。
函數(shù)主要功能如下所述:
- init函數(shù)是SegmentLogStorage初始化的入口函數(shù)求豫,調(diào)用load_meta函數(shù),list_segment函數(shù)和load_segment函數(shù)诉稍。
- load_meta函數(shù):從log_meta文件中讀取從SegmentLogStorage的第一個raft index值蝠嘉。
- list_segment函數(shù):建立起segment的范圍信息,并將范圍異常的segment文件刪除杯巨。范圍信息存儲在一個map表中蚤告,map的key是first_raft_index,value是segment對象。
- load_segments函數(shù):構(gòu)建出每個segment對應(yīng)的索引項服爷,通過解析segement內(nèi)容完成杜恰。索引項存儲在一個vector中获诈。至此,就可以根據(jù)范圍信息來定位到某個raft_index對應(yīng)的文件偏移箫章。
2.3 寫數(shù)據(jù)流程
寫數(shù)據(jù)到存儲引擎烙荷,會涉及到兩個函數(shù):
// append entry to log
int append_entry(const LogEntry* entry);
// append entries to log, return success append number
int append_entries(const std::vector<LogEntry*>& entries);
append_entry表示追加單條Log Entry到日志存儲引擎,append_entries用于同時追加多條Log Entry到日志存儲引擎檬寂。兩個函數(shù)主要流程相差不大终抽,我們以append_entries為例,分析一下寫入Log Entry的主要流程桶至。函數(shù)流程圖如下所示:
- 檢查日志連續(xù)性:主要檢查last_raft_index 是否和追加的Log Entry保持連續(xù)昼伴。
- 獲取Last_Segment:檢查last_segment是否超過Max_Segment_Size,如果超過則進行rolling操作(保存最后一個segment镣屹,并生成一個新的segment)圃郊。如果文件大小未超過Max_Segment_Size,則直接返回女蜈。
- 循環(huán)追加日志:追加Log Entry到文件末尾持舆。
- Last_Segment強制刷盤:調(diào)用fsync函數(shù)強制刷盤。
2.4 讀數(shù)據(jù)流程
根據(jù)raft_index讀取對應(yīng)的raft Log伪窖,根據(jù)我們前面提到的索引信息逸寓,braft很容易實現(xiàn),流程圖如下所示:
get_entry是入口函數(shù)覆山,get_segment函數(shù)主要是通過raft_index來定位到segment竹伸,通過之前建立的Map范圍信息很容易定位到。然后根據(jù)每個segment的Vector索引數(shù)組簇宽,定位到raft_index對應(yīng)的文件偏移信息勋篓。然后讀取文件。
2.5 刪除數(shù)據(jù)流程
刪除數(shù)據(jù)分為兩類:
- 從前往后刪除魏割,對應(yīng)的函數(shù)是:
SegmentLogStorage::truncate_prefix(const int64_t first_index_kept)
truncate_prefix函數(shù)先將first_index_kept保存到Log_meta文件中譬嚣,這樣保證了即使后續(xù)的文件刪除操作失敗時,也可以知道整個日志的起始raft_index是多少钞它。保存完first_index_kept之后拜银,將first_index_kept之前的segment文件全部刪除。
- 從后往前刪除须揣,對應(yīng)的函數(shù)是:
int SegmentLogStorage::truncate_suffix(const int64_t last_index_kept)
主要用于raft lib中刪除未達成一致的Log Entry盐股。根據(jù)last_index_kept找到對應(yīng)的文件偏移,然后截斷文件耻卡。如果跨文件疯汁,還需要刪除最后一個segment文件,然后再截斷之前一個segment的內(nèi)容卵酪。
3.測試
在test/test_log.cpp文件中幌蚊,包含SegmentLogStorage類中主要的接口函數(shù)的單元測試谤碳,對理解SegmentLogStorage有比較大的幫助。
4.總結(jié)
Braft的日志存儲引擎溢豆,主要用于存儲raft log蜒简。當(dāng)執(zhí)行完一次snapshot操作后,就可以進行Log Compaction漩仙。將snapshot之前的raft log全部刪除搓茬。這使得Braft可以將Log的索引信息全部存儲在內(nèi)存中,因為存儲引擎中的Raft Log Entry不會太大队他。這樣追加或讀取Raft Log只需要一次磁盤操作卷仑,性能方面有保證。