LevelDB解析

1. 整體架構

LevelDB是一個寫性能十分優秀的存儲引擎,是典型的LSM數(Log Structured-Merge Tree)實現。LSM樹的核心思想就是放棄部分讀的性能,換取最大的寫入能力。

LSM樹寫性能極高的原理,簡單地來說就是儘量減少隨機寫的次數。對於每次寫入操作,並不是直接將最新的數據駐留在磁盤中,而是將其拆分成:(1)一次日誌文件的順序寫;(2)一次內存中的數據插入。LevelDB正是實踐了這種思想,將數據首先更新在內存中,當內存中的數據達到一定的閾值,將這部分數據真正刷新到磁盤文件中,因而獲得了極高的寫性能。

1.1 memtable

memtable就是一個在內存中進行數據組織與維護的結構,所有的數據按用戶定義的排序方法排序之後按序存儲,等到其存儲內容的容量達到閾值時,便將其轉換成一個不可修改的memtable,與此同時創建一個新的memtable,供用戶繼續進行讀寫操作。memtable底層使用了一種跳錶數據結構。

immutable memtable

memtable的容量到達閾值時,便會轉換成一個不可修改的memtable,也稱爲immutable memtable。當一個immutable memtable被創建時,leveldb的後臺壓縮進程便會將利用其中的內容,創建一個sstable,持久化到磁盤文件中。

1.2 sstable

對於Level0層的sstable,存儲的數據範圍是重疊的,在查找時,需要遍歷Level0的所有sstable文件。對於其它Level層,存儲的數據範圍是不重疊的,可以通過每個sstable的範圍,定位到sstable文件。

Level0層的sstable是memtable文件compaction形成的,無法進行sstable文件的歸併。其它Level層的sstable文件是由上層的sstable文件compaction形象,可以保障各個sstable的數據範圍不重疊。

1.3 log

爲了增加讀取效率,日誌文件中按照block進行劃分,每個block的大小爲32KB。每個block中包含了若干個完整的chunk。

一條日誌記錄包含一個或多個chunk。每個chunk包含了一個7字節大小的header,前4字節是該chunk的校驗碼,緊接的2字節是該chunk數據的長度,以及最後一個字節是該chunk的類型。其中checksum校驗的範圍包括chunk的類型以及隨後的data數據。

chunk共有四種類型:full,first,middle,last。一條日誌記錄若只包含一個chunk,則該chunk的類型爲full。若一條日誌記錄包含多個chunk,則這些chunk的第一個類型爲first, 最後一個類型爲last,中間包含大於等於0個middle類型的chunk。chunk數據塊存儲多個entry數據。

1.4 manifest

manifest文件專用於記錄版本信息。leveldb採用了增量式的存儲方式,記錄每一個版本相較於上一個版本的變化情況。當進行compaction的時候,會有新的sstable文件生成或刪除。版本信息變化會封裝到VersionEdit中,追加到manifest文件中。

解析manifest得到版本變化VersionEdit:

VersionEdit{comparatorName='leveldb.BytewiseComparator', logNumber=null, previousLogNumber=null, lastSequenceNumber=null, compactPointers={}, newFiles={0=[FileMetaData{number=11, fileSize=216956, smallest=InternalKey{key=0, sequenceNumber=2004, valueType=VALUE}, largest=InternalKey{key=Tampa, sequenceNumber=2003, valueType=VALUE}, allowedSeeks=1073741824}, FileMetaData{number=8, fileSize=21735, smallest=InternalKey{key=0, sequenceNumber=1003, valueType=VALUE}, largest=InternalKey{key=Tampa, sequenceNumber=1002, valueType=VALUE}, allowedSeeks=1073741824}, FileMetaData{number=5, fileSize=21735, smallest=InternalKey{key=0, sequenceNumber=2, valueType=VALUE}, largest=InternalKey{key=Tampa, sequenceNumber=1, valueType=VALUE}, allowedSeeks=1073741824}]}, deletedFiles={}}
VersionEdit{comparatorName='null', logNumber=15, previousLogNumber=0, lastSequenceNumber=22004, compactPointers={}, newFiles={0=[FileMetaData{number=14, fileSize=216956, smallest=InternalKey{key=0, sequenceNumber=12005, valueType=VALUE}, largest=InternalKey{key=Tampa, sequenceNumber=12004, valueType=VALUE}, allowedSeeks=1073741824}]}, deletedFiles={}}
VersionEdit{comparatorName='null', logNumber=15, previousLogNumber=0, lastSequenceNumber=27685, compactPointers={0=InternalKey{key=Tampa, sequenceNumber=1, valueType=VALUE}}, newFiles={1=[FileMetaData{number=16, fileSize=216960, smallest=InternalKey{key=0, sequenceNumber=12005, valueType=VALUE}, largest=InternalKey{key=Tampa, sequenceNumber=12004, valueType=VALUE}, allowedSeeks=1073741824}]}, deletedFiles={0=[14, 11, 8, 5]}}

從manifest文件中,可以讀取leveldb各個層所包含的sstable文件,以及各個sstable存儲數據key的最大值和最小值。根據這些信息可以構建leveldb的數據存儲結構,實現數據的快速查詢。

current

記錄當前版本所指定的manifest文件。在一定情況下,會對manifest文件記錄的各個VersionEdit數據進行歸併,形成一個新的manifest文件,current文件會指向新的manifest文件。

2. SST文件結構

2.1 Data Block結構

DataBlock存儲key-value鍵值對數據。當向SST文件追加key-value的時候,首先添加到DataBlock中,當DataBlock的數據量大於數據塊默認值(4KB)時,會將DataBlock數據塊寫入磁盤,並保留DataBlock數據庫的索引信息,待SST寫入完成時,添加到IndexBlock。

DataBlock中所有key-value對都是嚴格按序存儲的。爲了節省存儲空間,LevelDB並不會爲每一對key-value對都存儲完整的key值,而是存儲與上一個key非共享的部分,避免key重複內容的存儲。

每間隔若干個key-value對,將爲該條記錄重新存儲一個完整的key,每個重新存儲完整key的點成爲Restart Point。Restart Points數據可以加速查找過程。

2.2 Filter Block結構

爲了加快SST中數據查詢的效率,在直接查詢DataBlock中的內容之前,會先根據FilterBlock中的過濾數據判定DataBlock中是否有需要查詢的數據,若判斷不存在,則無需對整個DataBlock進行數據查找。

FilterBlock存儲的是DataBlock數據的一些過濾信息,這些過濾數據一般指代布隆過濾器的數據,用於加快查詢的速度。每個DataBlock在FilterBlock中對應一個FilterData。

2.3 數據寫入操作

  1. 向SST中追加文件,會先將數據寫入內存中的DataBlock,如果DataBlock數據量大於指定大小,會將內存中的DataBlock寫入磁盤。在將DataBlock寫入前,會寫入RestartPoint信息,數據壓縮,CRC校驗。最後將DataBlock的索引信息寫入IndexBlock
  2. 當SST寫入完成,寫入內存中的FilterBlock、MetaIndexBlock、 IndexBlock、Footer信息

2.4 數據讀取操作

  1. 讀取Footer字段,獲取IndexBlockIndex、MetaIndexBlockIndex,根據offset和length讀取IndexBlock、MeatIndexBlock數據
  2. 根據IndexBlock索引數據獲取查詢數據的DataBlock,然後根據FilterBlock數據判定查詢數據是否在定位的DataBlock中,加速數據查詢
  3. 讀取定位到的DataBlock,根據RestartPoint數據定位查詢的數據塊中的Entry

3. Compaction

有兩種方式,一種是memtable文件足夠大,需要dump成sstable,這種情況叫做Minor Compaction;另外一種是不同層級間sstable文件的合併,叫做Major Compaction。

執行Compaction操作的觸發條件,可以分爲兩種:
1. size compaction:該層文件容量大於該層文件容量初始閾值,執行compaction操作,該層sstable文件合併到下一層
2. seek compaction:當sstable文件miss seek的次數超過一定閾值,執行compaction操作。

seek compaction的目的是,如果一個sstable文件miss seek的次數過高,需要到下一層sstable文件繼續檢索,執行seek compaction操作,可以將數據反映相互重疊的sstable文件歸併,減少查詢sstable文件的個數,提高抄襲效率。

leveldb中sstable文件分層組織的形式。假設只有level0和level1兩層,那麼勢必會導致level1層有很多的sstable文件,當level0層到level1層執行compaction操作的時候,可能會選取很多個sstable執行compaction操作,導致compaction操作是一個很耗時的操作。而分層組織sstable文件的方式則可以有效控制單次compaction所涉及的sstable文件數。

根據leveldb的分層原則,下一層的文件數量是上一層文件數量的10,可以直觀地理解level n單個sstable文件所覆蓋的範圍比level n+1廣。經過多級的稀疏,level n層sstable和level n+1文件執行歸併操作,涉及的sstable文件數可控。

文檔

  1. leveldb-handbook
  2. 淺析Bigtable和LevelDB的實現
  3. LevelDB之MANIFEST
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章