Braft的日誌存儲引擎實現分析

Braft的日誌存儲引擎實現分析

1.架構設計

1.1 函數介面說明

日誌存儲引擎是用於存儲raft lib產生的日誌。提供的介面如下:

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 logentrys 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 storages 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 storages 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只是一個抽象類,只定義了函數介面。具體的日誌操作由SegmentLogStorage實現。

1.2 存儲引擎的數據組織

SegmentLogStorage實現了LogStorage的全部介面。其數據組織格式如下:

  • segment名字為first_raft_index-last_raft_index,表示該segment的raft index範圍。
  • 只有最後一個segment可讀寫,其文件名為log_inprogress_first_raft_index,其他segment只讀。
  • segment文件對應的index entry,Segment文件初始化時構造出來,存儲在內存中。不會持久化到磁碟。因此追加一次Log Entry只會引起一次磁碟操作。

2.核心流程實現

2.1 存儲引擎的介面函數

2.2 存儲引擎的初始化

存儲引擎的初始化操作主要檢查文件信息,將segment的索引信息載入到內存,為讀寫操作做準備。

函數主要功能如下所述:

  • init函數是SegmentLogStorage初始化的入口函數,調用load_meta函數,list_segment函數和load_segment函數。
  • load_meta函數:從log_meta文件中讀取從SegmentLogStorage的第一個raft index值。
  • list_segment函數:建立起segment的範圍信息,並將範圍異常的segment文件刪除。範圍信息存儲在一個map表中,map的key是first_raft_index,value是segment對象。
  • load_segments函數:構建出每個segment對應的索引項,通過解析segement內容完成。索引項存儲在一個vector中。至此,就可以根據範圍信息來定位到某個raft_index對應的文件偏移。

2.3 寫數據流程

寫數據到存儲引擎,會涉及到兩個函數:

// 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到日誌存儲引擎。兩個函數主要流程相差不大,我們以append_entries為例,分析一下寫入Log Entry的主要流程。函數流程圖如下所示:

  • 檢查日誌連續性:主要檢查last_raft_index 是否和追加的Log Entry保持連續。
  • 獲取Last_Segment:檢查last_segment是否超過Max_Segment_Size,如果超過則進行rolling操作(保存最後一個segment,並生成一個新的segment)。如果文件大小未超過Max_Segment_Size,則直接返回。
  • 循環追加日誌:追加Log Entry到文件末尾。
  • Last_Segment強制刷盤:調用fsync函數強制刷盤。

2.4 讀數據流程

根據raft_index讀取對應的raft Log,根據我們前面提到的索引信息,braft很容易實現,流程圖如下所示:

get_entry是入口函數,get_segment函數主要是通過raft_index來定位到segment,通過之前建立的Map範圍信息很容易定位到。然後根據每個segment的Vector索引數組,定位到raft_index對應的文件偏移信息。然後讀取文件。

2.5 刪除數據流程

刪除數據分為兩類:

  • 從前往後刪除,對應的函數是:

SegmentLogStorage::truncate_prefix(const int64_t first_index_kept)

truncate_prefix函數先將first_index_kept保存到Log_meta文件中,這樣保證了即使後續的文件刪除操作失敗時,也可以知道整個日誌的起始raft_index是多少。保存完first_index_kept之後,將first_index_kept之前的segment文件全部刪除。

  • 從後往前刪除,對應的函數是:

int SegmentLogStorage::truncate_suffix(const int64_t last_index_kept)

主要用於raft lib中刪除未達成一致的Log Entry。根據last_index_kept找到對應的文件偏移,然後截斷文件。如果跨文件,還需要刪除最後一個segment文件,然後再截斷之前一個segment的內容。

3.測試

在test/test_log.cpp文件中,包含SegmentLogStorage類中主要的介面函數的單元測試,對理解SegmentLogStorage有比較大的幫助。

4.總結

Braft的日誌存儲引擎,主要用於存儲raft log。當執行完一次snapshot操作後,就可以進行Log Compaction。將snapshot之前的raft log全部刪除。這使得Braft可以將Log的索引信息全部存儲在內存中,因為存儲引擎中的Raft Log Entry不會太大。這樣追加或讀取Raft Log只需要一次磁碟操作,性能方面有保證。


推薦閱讀:

Raft 筆記
Indexed Shared Log
我對Raft的理解 - Two
Raft協議

TAG:分散式系統 | Raft |