LevelDB 實現分析

發表於2017-07-10

LevelDB 實現分析

LevelDB 介紹

LevelDB 是由 Google 開發的 key-value 非關係型資料庫儲存系統,是基於 LSM(Log-Structured-Merge Tree) 的典型實現,LSM 的原理是:當讀寫資料庫時,首先紀錄讀寫操作到 Op log 檔案中,然後再操作記憶體資料庫,當達到 checkpoint 時,則寫入磁碟,同時刪除相應的 Op log 檔案,後續重新生成新的記憶體檔案和 Op log 檔案。

LevelDB 內部採用了記憶體快取機制,也就是在寫資料庫時,首先會儲存在記憶體中,記憶體的儲存結構採用了 skip list 結構,待達到 checkpoint 時,才進行落盤操作,保證了資料庫的高效運轉。

LevelDB 總體架構

resources structure

如上圖所示,整個 LevelDB 由以下幾部分組成:

  1. Write(k,v),對外的介面
  2. Op log,操作日誌記錄檔案
  3. memtable,資料庫儲存的記憶體結構
  4. Immutable memtable,待落盤的資料庫記憶體資料
  5. sstable,落盤後的磁碟儲存結構
  6. manifest,LevelDB 元資訊清單,包括資料庫的配置資訊和中間使用的檔案列表
  7. current,當前正在使用的檔案清單

整體結構清晰緊湊,非常容易理解。

對外介面

整體介面分為:

資料庫建立和刪除

資料庫開啟

資料庫讀寫刪除操作

資料庫批處理操作

資料庫遍歷操作

獲取快照操作

Op log結構分析

LevelDB 使用的 Op log 日誌採用了檔案記錄的方式,且檔案使用了 mmap 方式操作,以提高效率。

Op log 儲存切分為 32KB 大小的資料塊,每個 32KB 資料塊儲存著 Op log,每 個Op log 格式如下:

resources structure

其中:

  1. CRC32 為 crc 校驗碼,保證資料的完整性
  2. Length,為 Op log 的資料長度
  3. Log Type,Op log 的型別,之所以會有型別,是由於 32KB 可能存不下一條 Op log,Op log 有可能跨資料塊,型別分為:
    • FULL:代表 Data 包含了所有的資料
    • FIRST:代表該 Data 是 Op log 的開始資料
    • MIDDLE:代表該 Data 是 Op log 的中間資料
    • LAST: 代表該 Data 是 Op log 的結束資料
  4. Data,為 Op log 的實際資料

memtable 結構分析

memtable 是 LevelDB 資料庫的記憶體儲存結構,採用了 skip list 結構儲存,如下圖所示:

skip list 是一種可以代替平衡樹的儲存結構,它採用概率的方式來保證平衡,而平衡樹則是採用嚴格的旋轉樹結構來保證平衡,複雜度會高一些。
對於 skip list,會有 n 層連結串列,其中 0 層儲存所有的值,越往上層,儲存的值越少。每當插入一個值時,會通過概率計算該值需要插入的最高層級 k,然後從 0~k-1 層,分別插入該值。

resources structure

其中每個表項的儲存結構如下:

key_size | key_value | sequence_num&type | value_size | value

其中:

sequence_num:表示操作的序列號,每一個資料項都會帶有一個序列號,用以表示資料的新舊程度。

type:表示資料的型別,分為:

  • kTypeValue:表明資料有效
  • kTypeDeletion:表明資料已經失效,在資料進行 delete 操作時會打上該標識

sstable 結構分析

sstable 作為落盤的儲存結構,每個 sstable 最大 2MB,從巨集觀來看,它屬於分層的結構,即:

  • level 0:最多儲存 4 個 sstable
  • level 1:儲存不超過 10MB 大小的 sstable
  • level 2:儲存不超過 100MB 大小的 sstable

level 3 及之後:儲存大小不超過上一級大小的 10 倍

之所以這樣分層,是為了提高查詢效率,也是 LevelDB 名稱的由來。當每一層超過限制時,會進行 compaction 操作,合併到上一層,遞迴進行。

從微觀的角度看,每個 sstable 檔案結構入下圖所示:

resources structure

其中:

  • Data Block 儲存具體的 k-v 資料
  • Meta Block 儲存索引過濾資訊,用於快速定位 key 是否存在於 Data Block 中
  • Meta Index Block 儲存 Meta Block 的偏移位置及大小
  • Index Block 儲存 Data Block 的偏移位置及大小
  • Footer 則儲存 Meta Index Block 和 Index Block 的偏移位置及大小,相當於二級索引,Footer 的結構如下:
    resources structure

另外 Data Block 及 Meta Block 的儲存格式是統一的,都是如下格式:

resources structure

其中 type 表示是否是壓縮儲存,目前 LevelDB 支援 key 值的 snappy 壓縮或者不壓縮。

而上圖中的 Block data 的格式則為:

resources structure

上圖有幾點要說明:

  1. 對於 Block data 中的第一項總是不壓縮儲存的,不壓縮儲存的項稱為 restarts,會被記錄在上圖的最尾部,同時每隔 k 個值(k 值可定製),都會儲存一個不壓縮的項,這些都稱為 restarts,都會被記錄在最尾部。
  2. 每個 restarts 表項會作為索引項儲存。
  3. 除了 restarts 表項以外,其它的表項則基於該 restarts 項,計算跟他相同部分和不同部分,上圖中的 shared_bytes 和 unshared_bytes 記錄了相同部分長度和不同部分的長度,key_delta 則記錄了不同的部分的值,value_length 和 value 則記錄了 value 部分的值。
  4. 壓不壓縮是可選的,預設會進行 snappy 壓縮。

對於 Meta Block 來說,它儲存了用於快速定位 key 是否在 Data Block 中的資訊,具體方法是:

  1. 採用了 bloom filter 的過濾機制,bloom filter 是一種 hash 機制,它對每一個 key,會計算 k 個 hash 值,然後在 k 個 bit 位記錄為 1。當查詢時,相應計算出 k 個 hash 值,然後比對 k 個 bit 位是否為 1,只要有一個不為 1,則不存在。
  2. 對於每一個 Data Block,所有的 key 值會傳入進行 bloom filter 的 hash 計算,每個 key 儲存 k 個 bit 位值。

版本管理

對於 LevelDB 來說,它採用了簡單的 sequence num 機制來管理,具體為:

  1. 對於 Op log 檔案,每一個 Op log 檔名中會包含一個唯一的 sequence num,每建立一個新的 Op log 檔案,sequence num 則加 1,sequence num 越大,則表示檔案越新,同時最新的 sequence num 會記錄下來。
  2. 對於每個 key-value 對,也會對應一個 sequence num,對於同一個 key,如果後續更新值時,sequence num 也會相應更新,這樣就可以根據 sequence num 的大小,找到最新的 key-value 對

新增特性

  1. 支援模糊查詢該功能支援 key 以模糊規則匹配的方式進行資料庫查詢,支援*和?兩種模糊規則查詢。
  2. 支援 JSON 格式資料儲存該功能支援 k-v 中,v以json格式傳入,後續可以通過關鍵字,查詢json裡面的資料。

結束語

LevelDB 短小精悍,程式碼執行效率高效,且通俗易懂,是一個非常不錯的 k-v 儲存系統。

注:圖片來源於網路

題圖:https://unsplash.com/photos/9wwF-VmSOrY By @eberhard grossgasteiger

相關文章