Leveldb源碼分析--14

9 LevelDB框架之1

到此爲止,基本上Leveldb的主要功能組件都已經分析完了,下面就是把它們組合在一起,形成一個高性能的k/v存儲系統。這就是leveldb::DB類。
這裏先看一下LevelDB的導出接口和涉及的類,後面將依次以接口分析的方式展開。
而實際上leveldb::DB只是一個接口類,真正的實現和框架類是DBImpl這個類,正是它集合了上面的各種組件。
此外,還有Leveldb對版本的控制,執行版本控制的是Version和VersionSet類。
在leveldb的源碼中,DBImpl和VersionSet是兩個龐然大物,體量基本算是最大的。對於這兩個類的分析,也會分散在打開、銷燬和快照等等這些功能中,很難在一個地方集中分析。
作者在文檔impl.html中描述了leveldb的實現,其中包括文件組織、compaction和recovery等等。下面的9.1和9.2基本都是翻譯子impl.html文檔。
在進入框架代碼之前,先來了解下leveldb的文件組織和管理。

9.1 DB文件管理

9.1.1 文件類型

對於一個數據庫Level包含如下的6種文件:

1 <dbname>/[0-9]+.log:db操作日誌
這就是前面分析過的操作日誌,log文件包含了最新的db更新,每個更新都以append的方式追加到文件結尾。當log文件達到預定大小時(缺省大約4MB),leveldb就把它轉換爲一個有序表(如下-2),並創建一個新的log文件。
當前的log文件在內存中的存在形式就是memtable,每次read操作都會訪問memtable,以保證read讀取到的是最新的數據。

2 <dbname>/[0-9]+.sst:db的sstable文件
這兩個就是前面分析過的靜態sstable文件,sstable存儲了以key排序的元素。每個元素或者是key對應的value,或者是key的刪除標記(刪除標記可以掩蓋更老sstable文件中過期的value)。
Leveldb把sstable文件通過level的方式組織起來,從log文件中生成的sstable被放在level 0。當level 0的sstable文件個數超過設置(當前爲4個)時,leveldb就把所有的level 0文件,以及有重合的level 1文件merge起來,組織成一個新的level 1文件(每個level 1文件大小爲2MB)。
Level 0的SSTable文件(後綴爲.sst)和Level>1的文件相比有特殊性:這個層級內的.sst文件,兩個文件可能存在key重疊。對於Level>0,同層sstable文件的key不會重疊。考慮level>0,level中的文件的總大小超過10^level MB時(如level=1是10MB,level=2是100MB),那麼level中的一個文件,以及所有level+1中和它有重疊的文件,會被merge到level+1層的一系列新文件。Merge操作的作用是將更新從低一級level遷移到最高級,只使用批量讀寫(最小化seek操作,提高效率)。

3 <dbname>/MANIFEST-[0-9]+:DB元信息文件
它記錄的是leveldb的元信息,比如DB使用的Comparator名,以及各SSTable文件的管理信息:如Level層數、文件名、最小key和最大key等等。

4 <dbname>/CURRENT:記錄當前正在使用的Manifest文件
它的內容就是當前的manifest文件名;因爲在LevleDb的運行過程中,隨着Compaction的進行,新的SSTable文件被產生,老的文件被廢棄。並生成新的Manifest文件來記載sstable的變動,而CURRENT則用來記錄我們關心的Manifest文件。
當db被重新打開時,leveldb總是生產一個新的manifest文件。Manifest文件使用log的格式,對服務狀態的改變(新加或刪除的文件)都會追加到該log中。
上面的log文件、sst文件、清單文件,末尾都帶着序列號,其序號都是單調遞增的(隨着next_file_number從1開始遞增),以保證不和之前的文件名重複。

5 <dbname>/log:系統的運行日誌,記錄系統的運行信息或者錯誤日誌。
6 <dbname>/dbtmp:臨時數據庫文件,repair時臨時生成的。
這裏就涉及到幾個關鍵的number計數器,log文件編號,下一個文件(sstable、log和manifest)編號,sequence。
所有正在使用的文件編號,包括log、sstable和manifest都應該小於下一個文件編號計數器。

9.1.2 Level 0

當操作log超過一定大小時(缺省是1MB),執行如下操作:
S1 創建新的memtable和log文件,並重導向新的更新到新memtable和log中;
S2 在後臺:
    S2.1 將前一個memtable的內容dump到sstable文件;
    S2.2 丟棄前一個memtable;
    S2.3 刪除舊的log文件和memtable
    S2.4 把創建的sstable文件放到level 0

9.2 Compaction

當level L的總文件大小查過限制時,我們就在後臺執行compaction操作。Compaction操作從level L中選擇一個文件f,以及選擇中所有和f有重疊的文件。如果某個level (L+1)的文件ff只是和f部分重合,compaction依然選擇ff的完整內容作爲輸入,在compaction後f和ff都會被丟棄。
另外:因爲level 0有些特殊(同層文件可能有重合),從level 0到level 1的compaction就需要特殊對待:level 0的compaction可能會選擇多個level 0文件,如果它們之間有重疊。
Compaction將選擇的文件內容merge起來,並生成到一系列的level (L+1)文件中,如果輸出文件超過設置(2MB),就切換到新的。當輸出文件的key範圍太大以至於和超過10個level (L+2)文件有重合時,也會切換。後一個規則確保了level (L+1)的文件不會和過多的level (L+2)文件有重合,其後的level (L+1) compaction不會選擇過多的level (L+2)文件。
老的文件會被丟棄,新創建的文件將加入到server狀態中。
Compaction操作在key空間中循環執行,詳細講一點就是,對於每個level,我們記錄上次compaction的ending key。Level的下一次compaction將選擇ending key之後的第一個文件(如果這樣的文件不存在,將會跳到key空間的開始)。
Compaction會忽略被寫覆蓋的值,如果更高一層的level沒有文件的範圍包含了這個key,key的刪除標記也會被忽略。

9.2.1 時間

Level 0的compaction最多從level 0讀取4個1MB的文件,以及所有的level 1文件(10MB),也就是我們將讀取14MB,並寫入14BM。
Level > 0的compaction,從level L選擇一個2MB的文件,最壞情況下,將會和levelL+1的12個文件有重合(10:level L+1的總文件大小是level L的10倍;邊界的2:level L的文件範圍通常不會和level L+1的文件對齊)。因此Compaction將會讀26MB,寫26MB。對於100MB/s的磁盤IO來講,compaction將最壞需要0.5秒。
如果磁盤IO更低,比如10MB/s,那麼compaction就需要更長的時間5秒。如果user以10MB/s的速度寫入,我們可能生成很多level 0文件(50個來裝載5*10MB的數據)。這將會嚴重影響讀取效率,因爲需要merge更多的文件。
解決方法1:爲了降低該問題,我們可能想增加log切換的閾值,缺點就是,log文件越大,對應的memtable文件就越大,這需要更多的內存。
解決方法2:當level 0文件太多時,人工降低寫入速度。
解決方法3:降低merge的開銷,如把level 0文件都無壓縮的存放在cache中。

9.2.2 文件數

對於更高的level我們可以創建更大的文件,而不是2MB,代價就是更多突發性的compaction。或者,我們可以考慮分區,把文件放存放多目錄中。
在2011年2月4號,作者做了一個實驗,在ext3文件系統中打開100KB的文件,結果表明可以不需要分區。
文件數       文件打開ms
1000          9
10000       10
100000     16

9.3 Recovery & GC

9.3.1 Recovery

Db恢復的步驟:
S1 首先從CURRENT讀取最後提交的MANIFEST
S2 讀取MANIFEST內容
S3 清除過期文件
S4 這裏可以打開所有的sstable文件,但是更好的方案是lazy open
S5 把log轉換爲新的level 0sstable
S6 將新寫操作導向到新的log文件,從恢復的序號開始

9.3.2 GC

垃圾回收,每次compaction和recovery之後都會有文件被廢棄,成爲垃圾文件。GC就是刪除這些文件的,它在每次compaction和recovery完成之後被調用。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章