LevelDB源碼閱讀(1)—— SSTable的生成

leveldb會按照不同版本組織數據(level-0 -> level-n,從新到舊),這些數據以SSTable格式存儲於磁盤上。一個SSTable文件可以看成一個基於磁盤、只讀的map,支持順序掃描,同時可以查找某個key。本文就來探究一下SSTable文件的格式,以及創建過程。

1. SSTable文件格式

| - - - - - - - - - - - - - - - - |

|   Data Blocks          |

| - - - - - - - - - - - - - - - - |

|   Filter Block           |

| - - - - - - - - - - - - - - - - |

|    Meta Index Block     |

| - - - - - - - - - - - - - - - - |

|    Index Block      |

| - - - - - - - - - - - - - - - - |

   Footer        |

| - - - - - - - - - - - - - - - - |


如上就是SSTable的文件格式,整個文件包括一系列的Block,Block是磁盤io以及內存cache的基本單位,通過Block讀寫可以均攤每次IO的開銷,利用局部性原理。Block的大小是用戶可配置的。

1) Data Blocks:存放kv對,即實際的數據。kv對會按照大小劃分成Block,無法保證所有的Block大小一致,但基本接近於配置的Block大小,但是當kv對數據大於該值時,會作爲一個Block。Block內部還包括其他的一些元數據,後文會深入介紹。

2) Filter Block: Filter用於確定SSTable是否包含一個key,從而在查找該key時,避免磁盤IO。Filter Block用於存儲Filter序列化後的結果。

3) Meta Index Block: 用於存放元數據,目前只會kv格式存儲Filter block的偏移量,key是filter.${FILTER_NAME},value是filter block在文件的偏移量。

4) Index Block: 對Data block的索引,保存了各個Block中最小key,從而確定了Block的key的範圍,加速某個key的搜索。

5) Footer: 元數據的元數據,其中包含Index Block和Meta Index Block的偏移量。Footer之所以在最後,是因爲文件生成時是順序追加的,而Footer的信息又依賴於之前的所有信息,所以只能在最後。由於包含了元數據,所以讀取SSTable時首要的就是加載footer。


2. 數據結構

BlockHandle: 指向文件中的一個Block,有兩個屬性Block的偏移量(offset_)和大小(size_)。

Footer: 表示SSTable文件的Footer,大小固定。

這兩個結構提供到string的序列化和反序列化的方法。

TableBuilder: 構造SSTable的入口。將一系列的kv對構造成SSTable。

BlockBuilder: 構造Block,對添加的kv對進行序列化。

FilterBlockBuilder: 構造Filter Block。

以上便是生成SSTable文件的主要數據結構。


3. BlockBuidler

上文提到Block是數據傳輸的基本單位,在Data Block中通過Block將連續的kv對打包處理,可以利用局部性原理。同時kv按key順序存儲,那麼同一個Block中key的重複內容比例會增加,可以通過壓縮提高空間利用率。

1) Block格式:

| - - Block Content: var len - - | - - Block Type: 1Byte - - | - - CRC: 4Byte - - |

一個Block包含3部分:

(1)Block Content:kv序列化後的內容,是可變長度的字節數組。

(2)Block Type:指定Block是否壓縮,1個字節。

(3)CRC:CRC校驗碼,用於數據完整測試,4個字節。

2) Block Content格式:

| - - KV Entries: var len - - | - - Restart Point Array: 4nBytes - - | - - Num Restart Point: 4Byte - - |

(1)KV Entries:一系列kv內容,Block的實際內容,是可變長度的字節數組。

(2)Restart Point Array:Restart Point數組,每個元素就是一個整形,4n字節。後文會介紹Restart Point的作用。

(3)Num Restart Point:指定了Restart Point數組的長度,4字節。
3)  KV Entry格式:

由於kv對按key順序存儲,所以對key採用前綴壓縮以節省空間。

| - - Shared: 4Byte - - | - - Non-shared: 4Byte - - | - - Val Len: 4Byte - - | - - Key Delta: var len - - | - - Value: var len - - |

(1)Shared:與前一個key共同前綴長度。

(2)Non-shared:key非共同前綴長度,Shared + Non-shared等於key的長度。

(3)Val len:value的長度。

(4)Key Delta:存儲去除共同前綴的key。

(5)Value:存放Value。

通過上面描述可以知道,在存儲一個key時,不會存其與前一個key的共同前綴,只會存不同的部分。那麼,從磁盤讀取一個key時,就需要先把前一個key讀出才能獲取完整的可以。這會存在一個問題,如果讀取最後一個key,那麼需要把[1, n-1]個key都讀出才能獲得完整內容。

LevelDB通過引入Restart Point來解決上述問題,每個Restart Point相當於前綴壓縮的重啓點。Restart Point指向一個key,它會存儲完整的內容,其Shared等於0。通過在一些kv中平均插入多個Restart Point,可以減少前綴解壓縮讀取的長度。默認,LevelDB中放置Restart Point的間隔爲16,保證最壞情況下最多隻要讀取15個key就能獲取一個key的完整內容。

同時,Restart Point指向的key也是排序的,可以把底層kv序列的二級索引,在進行key搜索時,先進行Restart Point的二分查找框定範圍,然後再在指定的key範圍內線性查找。


4. FilterBlock

Filter用於加快key搜索,避免無效的磁盤IO。LevelDB本身提供Bloom Filter。可以簡單把Filter當做是一個集合,用於判斷一個key是否存在於該集合。FilterBlock中存放的是SSTable文件中所有key組成的集合信息(就是Filter根據key進行序列化後的結果)。

1) FilterBlock格式:

| - - Encoded Filter Array: var len - - | - - Filter Offset Array: 4nByte - - | - - Offset of Offset Array: 4Byte - - | - - Base lg: 1Byte -- |

(1)Encoded Filter Array:經過Filter序列化的字節數組,由n個Filter的信息組成。

(2)Filter Offset Array:指定每個Filter在Encoded Filter Array中的偏移量。

(3)Offset of Offset Array:Filter Offset Array的偏移量。

(4)Base lg:2^(Base lg)是對數據塊構造Filter的最小size。LevelDB默認是2KB。

2) 流程:

LevelDB對於創建Filter源數據的大小有要求,不能小於2KB,這麼做是爲了防止源數據過小,導致取Filter的粒度過小,單位Block對應Filter的空間使用率過大,會比較浪費。

Encoded Filter Array與Data Block之間的對應關係稍微複雜些,當Block小於2KB時(比如1KB),那麼多個Block會使用一個Filter。如果Block大於2KB(比如4KB),那麼一個Block對應一個Filter。

在每次開始一個新的Block時,會調用FilterBuilder.StartBlock()方法,這裏會確定Block與Filter之間的對應關係,如果前一個Filter已經完成,會生成這個Filter。在所有Key添加完畢後,會調用FilterBuilder.Finish()方法進行整體序列化,並返回序列化後的結果。


5. TableBuilder

TableBuilder是對外生成SSTable的接口,通過Add方法接收一個個kv,依託於底層的BlockBuidler創建Block,如果當前的Block大小超過預設的值,會調用BlockBuilder的Finish方法進行序列化,然後追加到文件。在所有kv添加完畢後,調用TableBuilder.Finish方法追加元數據,包括Filter Block,Meta Index Block,Index Block以及Footer。

6. 總結

上述介紹的是SSTable的磁盤表現形式,設計的相當精巧,包括但不限於前綴壓縮,Restart Point的引入,Filter的源數據塊劃分等。下一篇會介紹SSTable的讀取,即內存表現形式。


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