資料庫1:台前幕後 —— 資料庫層實作概覽
我們人為将LevelDB分為了資料庫層和存儲引擎層,在前面部分介紹了存儲引擎相關的元件,包括:
- SSTable存儲結構實作,提供了在一個磁盤檔案裡讀取一個鍵的功能,并且可以疊代一個SSTable的所有鍵;
- MemTable記憶體結構的實作,提供了讀取一個鍵和寫入一個鍵的功能,疊代MemTable裡所有的鍵,以及可以将一個MemTable轉換為一個SSTable;
- Log将寫入持久化到磁盤上面,将随機寫入轉換為順序寫入;
- 疊代器對各種存儲元件疊代,以及定位某一個鍵。
利用這些元件提供的功能,就可以實作資料庫層。可以将資料庫層的功能分為兩個部分:
-
、Get
和Put
接口實作,這裡是LevelDB對外提供的操作接口,分别實作鍵值對的查找、插入和删除;Delete
- 版本管理和Compaction,随着資料的寫入,不斷的有MemTable轉換為SSTable,當有些鍵不斷的更新删除,有些Level的檔案太多時,影響了讀性能,需要進行Compaction,将低Level的SSTable Compaction到高Level的SSTable裡去,提高讀的效率。而Compaction是通過版本來管理的,當一次Compaction完成時,就會生成一個新版本。
接口實作
應用程式使用資料庫其實就是操作這些接口,接口是看得見的部分,這些接口的實作反而是比較簡單的部分。
先說
Put
操作,這個操作其實是最簡單的,就是向MemTable插入一條記錄,同時向Log插入一條記錄作持久化。而
Put
不但是一個插入操作,也是一個更新操作,當想更新一個Kv時,隻需要插入一個相同鍵的Kv即可,資料庫裡存在相同鍵的多個值,通過讀取來找到正确的值。
Delete
操作和
Put
操作很像,當需要删除一個鍵,隻需要寫入包含這個鍵的一條記錄,裡面有一個标記,标記這個鍵為删除,當讀取的時候讀取到這條辨別删除的記錄時,就可以忽略之前的記錄,傳回這條記錄不存在。
以上兩個操作說明LevelDB并不會真的去更新或者删除一條記錄,而隻會插入記錄,通過讀取來合并多條記錄。這樣就不需要去修改一個SSTable,隻需要順序寫資料,就減少了磁盤的随機讀取和寫入。
Put
和
Delete
數雖然很簡單,
Get
的操作就相對複雜了。因為
Put
和
Delete
都是插入一條記錄,是以可能同一個鍵有多條記錄存在,但是隻有最新的那條記錄有效的。如果最新的記錄是一條删除記錄,說明這個鍵是不存在的,否則值就是最新記錄的值。是以LevelDB在
Get
一個鍵時會按照從新到舊開始查詢:
- 首先查詢MemTable裡看鍵是否存在,因為MemTable裡資料是最新的資料;
- 如果一個MemTable寫滿了,會生成一個新的MemTable,而這個寫滿的MemTable的資料會轉換為一個SSTable,但是在轉換過程中這些資料可能被通路,這時候這個MemTable就會轉換成一個Immutable MemTable,這裡面的資料是次新的資料,如果Immutable MemTable存在,就要查找裡面是否包含需要的Kv;
- 如果上面兩個步驟都都沒查找鍵的話,就要查找SSTable了,SSTable先被寫到Level 0,然後再被Compaction到更高的層,是以Level越低,資料越新,就從Level 0開始查找,直到找到對應的鍵。
版本管理和Compaction
這一部分實際上是背景線程幹的事情,是看不到的部分。
随着資料的不斷寫入,不斷地将MemTable轉換為SSTable,而這些SSTable都在Level 0。而Level 0和其它Level相比,具有特殊性,其它Level裡SSTable的資料都是不重疊的,也就是當需要查找一個鍵時,可以找到唯一的一個SSTable,這個鍵隻有可能在這個SSTable裡,而Level 0的SSTable的鍵是有重疊的,也就是查找一個鍵時,這個鍵可能在多個SSTable裡,就需要搜尋多個SSTable。是以如果Level 0的SSTable太多了,
Get
的效率就會非常低。
對于更新和删除,是寫入一條記錄,如果對同一個鍵作多次更新和删除,那麼隻有最新的資料是有用的,這樣就會存在大量備援資料,浪費磁盤空間,并且降低了讀取的效率。
針對以上兩個問題,需要Compaction,Compaction就是将低Level的SSTable做歸并移到高的Level,并且消除備援的鍵值對。
LevelDB是支援快照的,也就是可以在某個時刻通路資料時,做一個快照,然後隻能看到那個時刻的資料,而在那個時刻後寫入和删除都是看不見的。查找時,就是指定那一時刻的所有SSTable,隻查詢這些SSTable裡的資料。然而随着Compaction的進行,SSTable的分布會發生變化,這時就需要版本管理了。當在通路一個舊的快照時,有一個版本對應這個快照,當Compaction進行時,生成新的版本,但是舊的版本還需要保留着,這樣舊快照就可以通路舊的版本。每一個快照就對應自己的版本,當一個版本不被任何快照引用時,這個版本就可以被删除了。
LevelDB是一個函數庫,自己是無法自動起背景線程的,這些背景線程是要依賴于調用方程序的。LevelDB的方式就是在調用方調用
Get
、
Put
和
Delete
時,會檢測一些條件是否符合,是否需要Compaction,如果需要的話,就會啟動背景線程開始Compaction,這個背景線程是屬于資料庫使用方的線程。
小結
LevelDB的資料庫層分為看得見的接口實作,以及看不見的背景Compaction,接下來會詳細介紹這兩部分的内容。