leveldb深度剖析-存儲結構(1)

從今天開始深入剖析leveldb源碼,在工作中也有用到leveldb(雖然沒出過問題),但是從個人興趣來說還是比較喜歡這款高效、簡單的數據庫。其實我們一直在使用leveldb,只是大家可能沒有發現。例如:Chrome(谷歌瀏覽器)底層存儲就是使用的leveldb。我是基於leveldb-v.1.20穩定版本進行分析。

爲什麼我第一篇關於leveldb的博客不是介紹leveldb的簡介、性能等基礎東西而是直接介紹數據結構,主要有兩個原因:

1)、關於leveldb簡介、性能分析現在網上有很多,不想重複造輪子了,所以我就不介紹了(也介紹不好)。

2)、通過我閱讀leveldb源碼以及網絡上其他人的博客,我發現了沒有一篇博客是介紹leveldb存儲結構,或者是說沒有圖形化介紹存儲結構。因爲leveldb是數據庫,如果能夠很清晰的描述出leveldb各種存儲結構是如何存儲的,對於閱讀代碼起到事半功倍的效果。

對於leveldb不是很瞭解的讀者,可以先去別的博客瞭解一下相關概念,然後再閱讀本博客是比較好的。

一、存儲文件

使用leveldb進行數據存儲,會生成以下幾個文件:

文件名 作用 備註
序號.log (000003.log) 操作日誌,主要記錄添加、刪除流程 例如:寫入一條記錄,會寫到.log文件中,然後在寫入到Memtable中,保證異常重啓數據不丟失。

MANIFEST-序號

(MANIFEST-000002)

清單文件,用於管理leveldb元數據信息 元信息文件
.ldb leveldb核心數據庫文件,用於存儲數據.level0~level6物理磁盤表現形式 從1.14版本以後不在以.sst作爲後綴名,網上的很多分析都是.sst後綴名。
 CURRENT 文本文件,用於指定當前使用的清單文件  
 LOCK LOCK鎖文件,用於併發  
 LOG leveldb的日誌文件,我們通常理解的日誌信息,方便定位bug  

各個文件具體作用,後面小節會進行詳細介紹說明。

二、數字7位壓縮存儲(非常經典)

著名的RPC框架Protobuf也使用了該算法.

關於該算法是網上有很多介紹,主體思想是:用7bit來表是數據,最高8bit位表示數據是否完整(1--未完整,0--完整)。

以int i = 300(佔用4字節)來舉例說明,

300的二進制表示位:         00000000  00000000  00000001  00101100

通過壓縮後只需要2字節:  10101100  00000010  

其中紅色(0/1)代表數據是否完整,藍色對應原來數據,編碼之後字節順序是反的。對於負數的編碼,需要先進行反碼操作在對其進行編碼,由於本片並不是介紹該算法的,大家可自行查詢相關文章。

後面用varint32,varint64表示uint32_t,uint64_t進行壓縮後數據類型。

三、MemTable

leveldb是key-value型數據,它在內存中組織形式以MemTable(類)呈現,而底層存儲結構是通過SkipList(跳錶)進行關聯各個key-value pair。下面介紹SkipList節點存儲結構,具體格式如下:

 說明:

1)橘色internal_key_length,value_length是可變長度,是對uint32_t進行7比特位壓縮。

2)leveldb爲了管理數據,在內部抽象出InternalKey對象。 internal_key由三部分組成: 用戶輸入key值,序列號,操作類型。
     序號:在leveldb源碼中定義爲uint64_t,但是實際可表示範圍是低56bit(高8bit不可用)。
     類型:在leveldb源碼中定義爲枚舉類型,kTypeDeletion(0x0)、kTypeValue(0x01)

3)value_length代表用戶輸入value值長度。

以上存儲結構作爲SkipList節點中實際內容,但是需要提醒節點數據結構中並沒有保存完整內容,而是保存了指針。數據內容保存在內存池中(MemTable::arena_)。當Memtable佔用空間爲4M(默認值)則將其進行壓縮存儲到level0。

四、.log和MANIFEST文件存儲格式

這兩個文件按照Block存儲,一個Block是32K,一個Block存儲多個記錄(Record),每個Record存儲格式如下:

字段名稱 含義
CRC 根據Type不同,設置不同的CRC
Length 代表buffer的長度
Type 當一個Record長度大於一個Block大小(32k),則需要進行分片處理,這裏Type就是用於標記分片,具體取值如下所示。
buffer 具體存儲內容,字節數組
enum RecordType {
  // Zero is reserved for preallocated files
  kZeroType = 0,

  kFullType = 1, //表示當前Record沒有超過32k

  // For fragments 表示當前Record超過了32k需要分片
  kFirstType = 2,  // 第一個分片
  kMiddleType = 3, // 中間分片
  kLastType = 4    // 最後一個分片
};

4.1、MANIFEST文件(清單)

上圖中的buffer是7bit壓縮後的VersionEdit對象,具體實現方法爲:VersionEdit::EncodeTo,壓縮後的數據格式如下:

 上面圖片說明

1)雖然是按行分開的,實際在文件系統中是連續存儲的。

2)Varint32代表對int32類型進行7bit壓縮存儲。

3)藍色內容爲類型,可參enmu Tag定義,每一個類型的數據有可能是不存在的。

      深橙色爲具體內容。

// Tag numbers for serialized VersionEdit.  These numbers are written to
// disk and should not be changed.
enum Tag {
  kComparator           = 1,
  kLogNumber            = 2,
  kNextFileNumber       = 3,
  kLastSequence         = 4,
  kCompactPointer       = 5,
  kDeletedFile          = 6,
  kNewFile              = 7,
  // 8 was used for large value refs
  kPrevLogNumber        = 9
};

4.2、.log文件

一個log文件是以多個block組成,一個block大小是32KB,一個block又由多個Record組成,其Record結構如下:

舉例說明: 

各個字段說明:

字段 說明
crc 數據校驗和
length 有效數據長度,即Record Data部分,不包含Record Header部分
RecordType 當前Record類型,具體參考下表。
SequenceNumber 當前Record中第一個key-value編號,最後一個key-value編號是通過SequenceNumber+count計算得知
count 當前Record中包含多少個key-value對
type 表示當前key-value操作類型,只有添加和刪除,具體參考下表
RecordType取值 說明
kZeroType = 0 保留字段
kFullType = 1 表示當block可以滿足此次數據存儲
kFirstType = 2 表示當前block剩餘空間不足,需要跨越多個block,存儲數據。這是一個分片
kMiddleType = 3 表示當前block剩餘空間不足,需要跨越多個block,存儲數據。這是中間分片
kLastType = 4 表示當前block剩餘空間不足,需要跨越多個block,存儲數據。這是最後一個分片,只有遇到這個類型才認爲是一個Record結束標誌。
type 說明
kTypeValue 向leveldb添加key-value
kTypeDeletion 從leveldb中刪除key-value,當是此類型時,只有key值,不需要value值。

 特別說明:當一個block剩餘空間小於7字節,則不再進行數據存儲,而是從新啓用一個新的block,但是爲了數據對齊,需要將剩餘空間補足32KB,以0進行填充。

下一篇繼續介紹ldb文件格式。

 

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