LevelDB原始碼剖析之Memtable 1

2021-06-25 19:20:50 字數 3797 閱讀 8064

memtable是leveldb很重要的一塊,leveldb的核心之一。我們肯定關注kv資料在memtable中是如何組織的,秘密在skip list中。

在leveldb中,所有記憶體中的kv資料都儲存在memtable中,物理disk則儲存在sstable中。在系統執行過程中,如果memtable中的資料占用記憶體到達指定值(options.write_buffer_size),則leveldb就自動將memtable轉換為memtable,並自動生成新的memtable,也就是copy-on-write機制了。

immutable memtable則被新的執行緒dump到磁碟中,dump結束則該immutable memtable就可以釋放了。因名知意,immutable memtable是唯讀的。

所以可見,最新的資料都是儲存在memtable中的,immutable memtable和物理sstable則是某個時點的資料。

為了防止系統down機導致記憶體資料memtable或者immutable memtable丟失,leveldb自然也依賴於log機制來保證可靠性了。

memtable提供了寫入kv記錄,刪除以及讀取kv記錄的介面,但是事實上memtable並不執行真正的刪除操作,刪除某個key的value在memtable內是作為插入一條記錄實施的,但是會打上乙個key的刪除標記,真正的刪除操作在後面的compaction過程中,lazy delete。

另外,memtable中的kv對是根據key排序的,leveldb在插入等操作時保證key的有序性。想想,前面看到的skip list不正是合適的人選嗎,因此memtable的核心資料結構是乙個skip list,memtable只是乙個介面類。當然隨之而來的乙個問題就是skip list是如何組織kv資料對的,在後面分析memtable的插入、查詢介面時我們將會看到答案。

先來看看memtable的介面:

void ref()

void unref();

iterator* newiterator();

void add(sequencenumber seq, valuetype type, const slice& key, const slice& value);

bool get(const lookupkey& key, std::string* value, status* s);

首先memtable是基於引用計數的機制,如果引用計數為0,則在unref中刪除自己,ref和unref就是幹這個的。

newiterator是返回乙個迭代器,可以遍歷訪問table的內部資料,很好的設計思想,這種方式隱藏了table的內部實現。外部呼叫者必須保證使用iterator訪問memtable的時候該memtable是live的。

add和get是新增和獲取記錄的介面,沒有delete,還記得前面說過,memtable的delete實際上是插入一條type為ktypedeletion的記錄。

先來看看memtable相關的整體類層次吧,並不複雜,還是相當清晰的。見圖4.4-1。

圖4.4-1

memtable是乙個kv儲存結構,那麼這個key肯定是個重點了,在分析介面實現之前,有必要仔細分析一下memtable對key的使用。

這裡面有5個key的概念,可能會讓人混淆,下面就來乙個乙個的分析。

4.5.1 internalkey & parsedinternalkey & user key

internalkey是乙個復合概念,是有幾個部分組合成的乙個key,parsedinternalkey就是對internalkey分拆後的結果,先來看看parsedinternalkey的成員,這是乙個struct:

>slice user_key;

>sequencenumber sequence;

>valuetype type;

也就是說internalkey是由user key + sequencenumber + valuetype組合而成的,順便先分析下幾個key相關的函式,它們是了解internal key和user key的關鍵。

首先是internalkey和parsedinternalkey相互轉換的兩個函式,如下。

>bool parseinternalkey (const slice& internal_key, parsedinternalkey* result);

函式實現很簡單,就是字串的拼接與把字串按位元組拆分,**略過。根據實現,容易得到internalkey的格式為:

|user key (string)|sequence number (7 bytes)|value type (1 byte)|

由此還可知道sequence number大小是7 bytes,sequence number是所有基於op log系統的關鍵資料,它唯一指定了不同操作的時間順序。

把user key放到前面的原因是,這樣對同乙個user key的操作就可以按照sequence number順序連續存放了,不同的user key是互不相干的,因此把它們的操作放在一起也沒有什麼意義。

另外使用者可以為user key定製比較函式,系統預設是字母序的。

下面的兩個函式是分別從internalkey中拆分出user key和value type的,非常直觀,**也附上吧。

inline slice extractuserkey(const slice& internal_key)

inline valuetype extractvaluetype(const slice& internal_key)

4.5.2 lookupkey & memtable key

memtable的查詢介面傳入的是lookupkey,它也是由user key和sequence number組合而成的,從其建構函式:lookupkey(const slice& user_key, sequencenumber s)中分析出lookupkey的格式為:

|size (int32變長)|user key (string)|sequence number (7 bytes)|value type (1 byte)|

兩點:>這裡的size是user key長度+8,也就是整個字串長度了;

>value type是kvaluetypeforseek,它等於ktypevalue。

>由於lookupkey的size是變長儲存的,因此它使用kstart_記錄了user key string的起始位址,否則將不能正確的獲取size和user key;

lookupkey匯出了三個函式,可以分別從lookupkey得到internal key,memtable key和user key,如下:

// return a key suitable for lookup in a memtable.

slice memtable_key() const

// return an internal key (suitable for passing to an internal iterator)

slice internal_key() const

// return the user key

slice user_key() const

其中start_是lookupkey字串的開始,end_是結束,kstart_是start_+4,也就是user key字串的起始位址。

leveldb原始碼剖析 編碼

leveldb是乙個google出品的單機kv資料庫。用c 編寫,量很小,大概只有1 2萬行。寫的可以用優雅來形容,毫無疑問是我至今看到的最優雅的c 而且由於 量比較小,可以直接通讀整個原始碼,了解乙個完整的kv系統的構建流程。是乙個很好的學習材料。這也是我第一次讀資料庫方面的原始碼,嘗試用部落格記...

leveldb原始碼剖析1 4 基礎概述之儲存格式

2 sstable檔案 3 manifest檔案 4 current檔案 前面講了leveldb的關鍵檔案,現在主要介紹各類關鍵檔案的資料格式,這對我們閱讀和理解leveldb原始碼是很有必要的。特別地,下面所有數字都是小端儲存。leveldb將put delete write封裝成writebat...

leveldb原始碼剖析2 4 核心設計之資料迭代器

2 其他迭代器 在leveldb中,迭代子模式被廣泛用及 幾乎貫穿於整個 之中。在leveldb中,主要的迭代器如下 注 除了圖中的關鍵迭代器,還有一些迭代器,例如emptyiterator。該迭代器主要功能是迭代sstable檔案資訊。其key為檔案的largest key value為檔案檔案序...