一 本文目的
對leveldb的總體設計框架分析(關於leveldb基本原理,此文不做闡述,讀者可以自行檢索文章閱讀即可),對leveldb中底層數據存儲數據格式,內存數據模型,compact,版本管理,快照等機制實現介紹以及整個leveldb實現源碼中各文件源碼的職責,方便快速對leveldb有個總體的掌握
二 各特性機制的實現
1.leveldb的底層數據格式存儲
leveldb底層數據格式,網上很多文章都有介紹,在此不做贅述,主要介紹一下上層怎么講數據寫入磁盤中。
leveldb中k-v數據寫入磁盤就是通過數據壓縮寫入的( CompactMemTable[內存壓縮至sst] & BackgroundCompaction[層間sst文件壓縮] ),上層都是通過TableBuilder對象來實現數據持久化的
主要的兩個接口:
# Add:將一行記錄加入一塊buffer中
#Finish:將k-v的記錄,按照table的格式(生成filter,metaindex,index,footer 塊),然后分配對各塊寫入到sst文件中
compact的使用大致過程:
1.壓縮過程中使用迭代器模式,按序遍歷輸入需要壓縮的sst文件中key-val鍵值對。
2. 丟棄key過期記錄(比如:一個key的多次修改,只保留最新的一條記錄,另外還有快照,這是后話),對沒有過期的記錄,將會調用TableBuilder.Add接口添加到TableBuilder的緩存區中,
3. 直到緩存區中數據大小達到閾值(用戶指定)。將會調用TableBuilder.Finish 生成sst表格式數據並持久化。
2. 內存數據模型
內存模型是使用跳表的數據結構的方式來進行管理維護的。
# 跳表在leveldb中定義的是一個通用的數據結構,需要外部傳入節點key的compare對象。
#MemTable 使用組合的方式使用跳表,自定義compare對象。
# 其中需要注意有意思的一點MemTable 存儲到跳表結構中的節點是包含(key+val),跳表中key值得比較對象是通過外部定義傳入的,MemTable使用的是下面的比較函數,從函數中可以看出需要對節點提取真正的userKey進行比較。

3.Compact過程:
compact主要分為一下幾步
1.選擇需要compact的level,通過VersionSet.PickCompaction函數來決定需要compact哪一層,改層哪些文件需要compact
#至於為什么由VersionSet來決定,是因為VersionSet管理當前整個leveldb的文件組織結構等信息,后面再版本管理中會進行詳細說明
2.對需要compact的文件建立迭代器,迭代器按key的排序依次訪問所有的記錄
3.依次遍歷所有的記錄,判斷需要丟棄記錄,對需要保留的記錄,會調用TableBuilder.Add接口加入sst緩存區中
記錄丟棄的判斷條件
#同一個key非第一次出現,並且記錄對應的序列號小於最舊的快照的序列號(說明該記錄不需要快照備份)
# 該key值插入一個刪除操作,並且不需要快照備份
4.sst緩存區寫滿,就會生成一個完整的sst文件格式,然后持久化到磁盤上
5.將壓縮過程中涉及到的文件變更(例如:老sst刪除,新sst生成)加入到版本管理中的version_edit中
#注:此處不涉及老sst文件的清理,只是記錄當前一次compact操作導致哪些文件需要被刪除,真正刪除操作由其他過程執行
6.刪除過期文件
# 當前compact出來的新sst文件 & 所有版本version管理的sst文件,都會加入livefiles集合中
#不在livefiles集合中的文件全部認為是過期文件,需要刪除。
4. 版本管理
版本管理是leveldb中極其重要的模塊,要想理解整個leveldb必須理解其為什么需要 & 如何實現版本管理的
4.1 為什么需要版本管理
假設一種場景:一個用戶發起對某個sst文件讀取操作,數據讀取到了一半,此時compact完成,由於compact是獨立的一個線程,此時sst文件會被清理掉了,此時用戶讀操作出錯
所以一句話就是:管理磁盤上的文件,保證leveldbdb數據的准確性
4.2 如何實現版本管理
基本概念:
version :一個version對應一次數據文件變更的記錄,比如compact會導致文件發現變化(老的sst文件,新生成sst文件),所以需要記錄當前compact過程結束后,在當前數據庫狀態下有哪些文件,
#主要數據結構:std::vector<FileMetaData*> files_[config::kNumLevels];記錄管理每一層sst文件
versionSet:由於每一次compact后都會產生一個version,所以需要將這些version管理起來,采用雙向鏈表version按先后生成的順序管理起來
versionEdit:變化增量,每次compact時記錄增量,主要是增加了哪些文件,需要刪除哪些文件,通過上一次Version+versionEdit就會得到當前的Version
#主要數據結構:
#DeletedFileSet deleted_files_;
#std::vector<std::pair<int, FileMetaData>> new_files_;
實現大致思路是:
每次在compact后生成versionEdit,然后通過調用versionSet.LogAndApply將versionEdit應用到當前版本生成最新的版本,然后加入versionSet鏈表中成為當前版本。
# 幾個問題:
1.版本管理的使用場景
在每次對文件的使用(比如讀)都會獲取當前版本,然后會將該版本的引用計數+1,(讀)完成后會將引用計數-1
2.版本什么時候會被刪除
當一個版本沒有了引用(最新版本在創建時引用計數自動+1,知道當下一個更新的版本加入是ref-1),系統會自動從versinSet的雙向鏈表中摘除掉
5.快照機制
# 管理數據結構:快照雙向鏈表,將所有的快照使用雙向鏈表維護管理起來
# 快照數據結構主要字段
SnapshotImpl* prev_; //鏈表相關
SnapshotImpl* next_; //鏈表相關
const SequenceNumber sequence_number_; //快照的sequence_number_
#實現的主要思想:
leveldb中對於每次插入,系統維護一個全局自增的seq序列號,每一個key-val記錄插入時都會帶上該序列號,快照只需要獲取當前一個seq序列號並將生成的快照加入快照列表中(防止compact時將已經打了快照的記錄丟棄掉)。
訪問通過指定key & 快照序列號,系統先查找滿足key值相同 && 記錄序列號不能大於快照序列號的記錄(持久化時相同key值記錄是按照序列號有序存放的),返回給用戶,從而實現快照。
#如何保證快照的記錄不被刪除呢?
所有刪除的過程只有compact時進行,compact邏輯中,遍歷所有需要壓縮的文件的key值,會判斷該key能否丟棄
其中有一個條件是,如果當前key有快照引用,就不能compact掉,從而保證打了快照的數據不會被丟棄。
三 源碼結構
主要通過源碼的目錄結構以及闡述關鍵目錄和源文件的職責的方式來展示源碼的整個架構。
cmake:cmake的相關文件
db:主要機制的實現,包括版本管理,compact,業務讀寫等功能機制實現;
doc:文檔;
helpers/memenv:簡單完全內存的文件系統,提供操作目錄文件接口;
include/leveldb:頭文件,外部工程使用leveldb時引用的頭文件;
port:平台相關的實現,主要提供posix/android相關支持;
table:定義了整個leveldb的持久化存儲的數據結構
util:通用功能實現。
主要介紹的是db & table,這兩部分是整個leveldb的精髓
table:完成了整個leveldb持久化層的數據格式的定義以及實現
# block + block_builder :定義了block格式以及block如何生成的實現,包括block塊中重啟點等技術細節,將block的訪問抽象成對迭代器模式的訪問
# filter_block:定義了過濾器的實現
# two_level_iterator & iterator_wrapper & iterator & merger & two_level_iterator:定義了各種迭代器,從而屏蔽底層數據訪問細節
#其中two_level_iterator:封裝了索引迭代器和數據迭代器的操作,本質上是一個二重循環,來實現key的有序遍歷
for(遍歷索引){
for(遍歷當前索引指向的block)
}
# table & table_build:定義了sst文件的數據格式以及如何生成sst的過程
db:實現了包括上面提到的各種機制,主要包括版本管理,compact,容災恢復等具體的實現細節
#db_impl:定義個數據庫的各種接口以及compact等數據庫特性
#log_format & log_reader & log_writer:定義了log文件格式和讀寫,此處的log文件用來實現備份容災的功能的
#memtable & skiplist:定義了leveldb如何使用跳表來實現數據在內存中的有序存儲
#snapshot:負責管理快照,使用鏈表的方式對快照進行管理
#version_edit & version_set:負責版本管理的操作

