LevelDB 介紹
LevelDB 是由 Google 開發的 key-value 非關係型資料庫儲存系統,是基於 LSM(Log-Structured-Merge Tree) 的典型實現,LSM 的原理是:當讀寫資料庫時,首先紀錄讀寫操作到 Op log 檔案中,然後再操作記憶體資料庫,當達到 checkpoint 時,則寫入磁碟,同時刪除相應的 Op log 檔案,後續重新生成新的記憶體檔案和 Op log 檔案。
LevelDB 內部採用了記憶體快取機制,也就是在寫資料庫時,首先會儲存在記憶體中,記憶體的儲存結構採用了 skip list 結構,待達到 checkpoint 時,才進行落盤操作,保證了資料庫的高效運轉。
LevelDB 總體架構
如上圖所示,整個 LevelDB 由以下幾部分組成:
- Write(k,v),對外的介面
- Op log,操作日誌記錄檔案
- memtable,資料庫儲存的記憶體結構
- Immutable memtable,待落盤的資料庫記憶體資料
- sstable,落盤後的磁碟儲存結構
- manifest,LevelDB 元資訊清單,包括資料庫的配置資訊和中間使用的檔案列表
- current,當前正在使用的檔案清單
整體結構清晰緊湊,非常容易理解。
對外介面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
DB() { }; virtual ~DB(); static Status Open(const Options& options, const std::string& name, DB** dbptr); virtual Status Put(const WriteOptions& options, const Slice& key, const Slice& value) = 0; virtual Status Delete(const WriteOptions& options, const Slice& key) = 0; virtual Status Write(const WriteOptions& options, WriteBatch* updates) = 0; virtual Status Get(const ReadOptions& options, const Slice& key, std::string* value) = 0; virtual Iterator* NewIterator(const ReadOptions& options) = 0; virtual const Snapshot* GetSnapshot() = 0; virtual void ReleaseSnapshot(const Snapshot* snapshot) = 0; |
整體介面分為:
資料庫建立和刪除
1 2 |
DB() { }; virtual ~DB(); |
資料庫開啟
1 2 3 |
static Status Open(const Options& options, const std::string& name, DB** dbptr); |
資料庫讀寫刪除操作
1 2 3 4 5 6 |
virtual Status Put(const WriteOptions& options, const Slice& key, const Slice& value) = 0; virtual Status Delete(const WriteOptions& options, const Slice& key) = 0; virtual Status Get(const ReadOptions& options, const Slice& key, std::string* value) = 0; |
資料庫批處理操作
1 |
virtual Status Write(const WriteOptions& options, WriteBatch* updates) = 0; |
資料庫遍歷操作
1 |
virtual Iterator* NewIterator(const ReadOptions& options) = 0; |
獲取快照操作
1 2 |
virtual const Snapshot* GetSnapshot() = 0; virtual void ReleaseSnapshot(const Snapshot* snapshot) = 0; |
Op log結構分析
LevelDB 使用的 Op log 日誌採用了檔案記錄的方式,且檔案使用了 mmap 方式操作,以提高效率。
Op log 儲存切分為 32KB 大小的資料塊,每個 32KB 資料塊儲存著 Op log,每 個Op log 格式如下:
其中:
- CRC32 為 crc 校驗碼,保證資料的完整性
- Length,為 Op log 的資料長度
- Log Type,Op log 的型別,之所以會有型別,是由於 32KB 可能存不下一條 Op log,Op log 有可能跨資料塊,型別分為:
- FULL:代表 Data 包含了所有的資料
- FIRST:代表該 Data 是 Op log 的開始資料
- MIDDLE:代表該 Data 是 Op log 的中間資料
- LAST: 代表該 Data 是 Op log 的結束資料
- Data,為 Op log 的實際資料
memtable 結構分析
memtable 是 LevelDB 資料庫的記憶體儲存結構,採用了 skip list 結構儲存,如下圖所示:
skip list 是一種可以代替平衡樹的儲存結構,它採用概率的方式來保證平衡,而平衡樹則是採用嚴格的旋轉樹結構來保證平衡,複雜度會高一些。
對於 skip list,會有 n 層連結串列,其中 0 層儲存所有的值,越往上層,儲存的值越少。每當插入一個值時,會通過概率計算該值需要插入的最高層級 k,然後從 0~k-1 層,分別插入該值。
其中每個表項的儲存結構如下:
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 檔案結構入下圖所示:
其中:
- 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 的結構如下:
另外 Data Block 及 Meta Block 的儲存格式是統一的,都是如下格式:
其中 type 表示是否是壓縮儲存,目前 LevelDB 支援 key 值的 snappy 壓縮或者不壓縮。
而上圖中的 Block data 的格式則為:
上圖有幾點要說明:
- 對於 Block data 中的第一項總是不壓縮儲存的,不壓縮儲存的項稱為 restarts,會被記錄在上圖的最尾部,同時每隔 k 個值(k 值可定製),都會儲存一個不壓縮的項,這些都稱為 restarts,都會被記錄在最尾部。
- 每個 restarts 表項會作為索引項儲存。
- 除了 restarts 表項以外,其它的表項則基於該 restarts 項,計算跟他相同部分和不同部分,上圖中的 shared_bytes 和 unshared_bytes 記錄了相同部分長度和不同部分的長度,key_delta 則記錄了不同的部分的值,value_length 和 value 則記錄了 value 部分的值。
- 壓不壓縮是可選的,預設會進行 snappy 壓縮。
對於 Meta Block 來說,它儲存了用於快速定位 key 是否在 Data Block 中的資訊,具體方法是:
- 採用了 bloom filter 的過濾機制,bloom filter 是一種 hash 機制,它對每一個 key,會計算 k 個 hash 值,然後在 k 個 bit 位記錄為 1。當查詢時,相應計算出 k 個 hash 值,然後比對 k 個 bit 位是否為 1,只要有一個不為 1,則不存在。
- 對於每一個 Data Block,所有的 key 值會傳入進行 bloom filter 的 hash 計算,每個 key 儲存 k 個 bit 位值。
版本管理
對於 LevelDB 來說,它採用了簡單的 sequence num 機制來管理,具體為:
- 對於 Op log 檔案,每一個 Op log 檔名中會包含一個唯一的 sequence num,每建立一個新的 Op log 檔案,sequence num 則加 1,sequence num 越大,則表示檔案越新,同時最新的 sequence num 會記錄下來。
- 對於每個 key-value 對,也會對應一個 sequence num,對於同一個 key,如果後續更新值時,sequence num 也會相應更新,這樣就可以根據 sequence num 的大小,找到最新的 key-value 對
新增特性
- 支援模糊查詢該功能支援 key 以模糊規則匹配的方式進行資料庫查詢,支援*和?兩種模糊規則查詢。
- 支援 JSON 格式資料儲存該功能支援 k-v 中,v以json格式傳入,後續可以通過關鍵字,查詢json裡面的資料。
結束語
LevelDB 短小精悍,程式碼執行效率高效,且通俗易懂,是一個非常不錯的 k-v 儲存系統。
注:圖片來源於網路
題圖:https://unsplash.com/photos/9wwF-VmSOrY By @eberhard grossgasteiger