【leveldb源碼閱讀】之LOG

理清leveldb基本架構圖之後,我又標出了一些比較重要(主要因爲我是小白)的信息點,如下:

在這裏插入圖片描述

[comment<>(在代碼 [db/filename.h]可以看到leveldb的幾種文件類型)

// Owned filenames have the form:
//    dbname/CURRENT
//    dbname/LOCK
//    dbname/LOG
//    dbname/LOG.old
//    dbname/MANIFEST-[0-9]+
//    dbname/[0-9]+.(log|sst|ldb)

我們先按照寫數據的順序對着代碼依次介紹。

LOG

寫入數據的時候,最開始會寫入到log文件中,由於是順序寫入文件,所以寫入速度很快,可以馬上返回。

log日誌的格式說明[doc/log_format.md]

  • 一個Log文件由多個Block組成,每個Block大小爲32KB。

  • 一個Block內部又有多個Record組成,Record分爲四種類型(還有一個留爲預分配文件[db/log_format.h]):

    • Full:一個Record佔滿了整個Block存儲空間。
    • First:一個Block的第一個Record。
    • Last:一個Block的最後一個Record。
    • Middle:其餘的都是Middle類型的Record。
  • 一個Record由幾部分組成:

    • Header部分
    • 32位長度的CRC
    • 16位長度的Length:存儲數據部分長度。
    • 8位長度的Type:存儲Record類型,就是上面說的四種類型。

1.1 寫log

寫過程舉個例子,我們現在想把這些數據寫入:

    A: 長度 1000
    B: 長度 97270
    C: 長度 8000

我們先裝第一個block。可以看到A數據很小,所以用FULL 就可以裝下,到這時第一個record還剩31761B的空間。

下面繼續裝B,但是它好大,要把它切分再裝。B的第一部分要接着第一個Record去裝,所以在這裏它的RecordTypeFirst,裝了31761B的數據。這時候第一個block已經滿了,再來一個block,這個block刨除record的hearder、crc等部分,還可以裝32761B的數據,當然這還是不夠B裝的,沒關係,我們接着開一個block裝。而這時,對於B的第二個部分的RecordType就是Second了。這時B還剩餘32655B的數據,一個block是裝得下的,還剩了6B的空間,我們留出來做trailer

致鬱C,和A一樣,都是FULL record,落在第四個block中。

上述過程可以用一張圖直觀表示:

總結一下,log有多個固定大小的block組成 ,block又由record組成,record是連續的,數據可能會被拆到不同的record上。

寫操作類Writer中的接口函數是AddRecord

Status AddRecord(const Slice& slice);

簡單看一下這個函數:

status:狀態
block_offset_ : 當前block用(偏移)到哪裏了
leftover : 當前block還剩多少
left:待寫入數據
kBlockSize:32(32768,Bytes)
kHeaderSize:74+2+1,Bytes)
type:即RecordType


while(status_is_ok &&  left>0) {
    if (leftover < kHeaderSize) {
        // 用0填充
    }
    // 根據left、block_offset_,更新RecordType
    // 真正寫入過程由EmitPhysicalRecord完成,包括生成一個record頭部,追加數據
    // 更新status
    // 更新left
}

1.2 讀log

讀操作類Writer中的接口函數是ReadRecord

bool ReadRecord(Slice* record, std::string* scratch);
  // 真正讀入過程由ReadPhysicalRecord實現:從文件中每次讀取一個Block,Read內部會做偏移,保證按順序讀取,並判斷各種badrecord的情況
  // 根據recordtype,向switch指向的內存中追加數據
  switch(recordtype) {
      case Full:
      case First:
      case Middle:
      case Last:
  }

Slice是一個結構體,其中只有兩個成員,一個指向外存的指針,一個是大小。

代碼實現用了switch...case真是棒呆。具體的代碼邏輯我加了一些註釋,可以具體瞭解一下。

參考資料

LevelDB設計與實現 - 基礎篇

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章