Braft的日志存儲(chǔ)引擎實(shí)現(xiàn)分析

Braft的日志存儲(chǔ)引擎實(shí)現(xiàn)分析

1.架構(gòu)設(shè)計(jì)

1.1 函數(shù)接口說明

日志存儲(chǔ)引擎是用于存儲(chǔ)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只是一個(gè)抽象類,只定義了函數(shù)接口。具體的日志操作由SegmentLogStorage實(shí)現(xiàn)。

1.2 存儲(chǔ)引擎的數(shù)據(jù)組織

SegmentLogStorage實(shí)現(xiàn)了LogStorage的全部接口。其數(shù)據(jù)組織格式如下:


image.png
  • segment名字為first_raft_index-last_raft_index,表示該segment的raft index范圍。
  • 只有最后一個(gè)segment可讀寫,其文件名為log_inprogress_first_raft_index,其他segment只讀。
  • segment文件對(duì)應(yīng)的index entry,Segment文件初始化時(shí)構(gòu)造出來,存儲(chǔ)在內(nèi)存中。不會(huì)持久化到磁盤。因此追加一次Log Entry只會(huì)引起一次磁盤操作。

2.核心流程實(shí)現(xiàn)

2.1 存儲(chǔ)引擎的接口函數(shù)

2.2 存儲(chǔ)引擎的初始化

存儲(chǔ)引擎的初始化操作主要檢查文件信息,將segment的索引信息加載到內(nèi)存,為讀寫操作做準(zhǔn)備。


image.png

函數(shù)主要功能如下所述:

  • init函數(shù)是SegmentLogStorage初始化的入口函數(shù),調(diào)用load_meta函數(shù),list_segment函數(shù)和load_segment函數(shù)。
  • load_meta函數(shù):從log_meta文件中讀取從SegmentLogStorage的第一個(gè)raft index值。
  • list_segment函數(shù):建立起segment的范圍信息,并將范圍異常的segment文件刪除。范圍信息存儲(chǔ)在一個(gè)map表中,map的key是first_raft_index,value是segment對(duì)象。
  • load_segments函數(shù):構(gòu)建出每個(gè)segment對(duì)應(yīng)的索引項(xiàng),通過解析segement內(nèi)容完成。索引項(xiàng)存儲(chǔ)在一個(gè)vector中。至此,就可以根據(jù)范圍信息來定位到某個(gè)raft_index對(duì)應(yīng)的文件偏移。

2.3 寫數(shù)據(jù)流程

寫數(shù)據(jù)到存儲(chǔ)引擎,會(huì)涉及到兩個(gè)函數(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到日志存儲(chǔ)引擎,append_entries用于同時(shí)追加多條Log Entry到日志存儲(chǔ)引擎。兩個(gè)函數(shù)主要流程相差不大,我們以append_entries為例,分析一下寫入Log Entry的主要流程。函數(shù)流程圖如下所示:


image.png
  • 檢查日志連續(xù)性:主要檢查last_raft_index 是否和追加的Log Entry保持連續(xù)。
  • 獲取Last_Segment:檢查last_segment是否超過Max_Segment_Size,如果超過則進(jìn)行rolling操作(保存最后一個(gè)segment,并生成一個(gè)新的segment)。如果文件大小未超過Max_Segment_Size,則直接返回。
  • 循環(huán)追加日志:追加Log Entry到文件末尾。
  • Last_Segment強(qiáng)制刷盤:調(diào)用fsync函數(shù)強(qiáng)制刷盤。

2.4 讀數(shù)據(jù)流程

根據(jù)raft_index讀取對(duì)應(yīng)的raft Log,根據(jù)我們前面提到的索引信息,braft很容易實(shí)現(xiàn),流程圖如下所示:


image.png

get_entry是入口函數(shù),get_segment函數(shù)主要是通過raft_index來定位到segment,通過之前建立的Map范圍信息很容易定位到。然后根據(jù)每個(gè)segment的Vector索引數(shù)組,定位到raft_index對(duì)應(yīng)的文件偏移信息。然后讀取文件。

2.5 刪除數(shù)據(jù)流程

刪除數(shù)據(jù)分為兩類:

  • 從前往后刪除,對(duì)應(yīng)的函數(shù)是:
SegmentLogStorage::truncate_prefix(const int64_t first_index_kept)

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

  • 從后往前刪除,對(duì)應(yīng)的函數(shù)是:
int SegmentLogStorage::truncate_suffix(const int64_t last_index_kept) 

主要用于raft lib中刪除未達(dá)成一致的Log Entry。根據(jù)last_index_kept找到對(duì)應(yīng)的文件偏移,然后截?cái)辔募?。如果跨文件,還需要?jiǎng)h除最后一個(gè)segment文件,然后再截?cái)嘀耙粋€(gè)segment的內(nèi)容。

3.測試

在test/test_log.cpp文件中,包含SegmentLogStorage類中主要的接口函數(shù)的單元測試,對(duì)理解SegmentLogStorage有比較大的幫助。

4.總結(jié)

Braft的日志存儲(chǔ)引擎,主要用于存儲(chǔ)raft log。當(dāng)執(zhí)行完一次snapshot操作后,就可以進(jìn)行Log Compaction。將snapshot之前的raft log全部刪除。這使得Braft可以將Log的索引信息全部存儲(chǔ)在內(nèi)存中,因?yàn)榇鎯?chǔ)引擎中的Raft Log Entry不會(huì)太大。這樣追加或讀取Raft Log只需要一次磁盤操作,性能方面有保證。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容