LevelDB源碼解析19. FilterBlockBuilder
FilterBlock
這裡主要介紹的代碼,位於table/filter_block.h
和table/filter_block.cc
。
在本文中將不再區分filter block
和meta block
。
接下來將要介紹的是FilterBlock
,實質上就是SST文件裡面的meta block
,由於目前只有這麼一種meta block
並用是用來做為filter。所以也經常被稱之為filter block
。
使用方式
在查看類的內部定義之前,可以先看一下這個類是如何使用的。這將有助於理解後面FilterBlockBuilder
類的含義。
TEST(FilterBlockTest, SingleChunk) {
FilterBlockBuilder builder(&policy_);
builder.StartBlock(100);
builder.AddKey("foo");
builder.AddKey("bar");
builder.AddKey("box");
builder.StartBlock(200);
builder.AddKey("box");
builder.StartBlock(300);
builder.AddKey("hello");
Slice block = builder.Finish();
FilterBlockReader reader(&policy_, block);
ASSERT_TRUE(reader.KeyMayMatch(100, "foo"));
ASSERT_TRUE(reader.KeyMayMatch(100, "bar"));
ASSERT_TRUE(reader.KeyMayMatch(100, "box"));
ASSERT_TRUE(reader.KeyMayMatch(100, "hello"));
ASSERT_TRUE(reader.KeyMayMatch(100, "foo"));
ASSERT_TRUE(! reader.KeyMayMatch(100, "missing"));
ASSERT_TRUE(! reader.KeyMayMatch(100, "other"));
}
類的定義
// A FilterBlockBuilder is used to construct all of the filters for a
// particular Table. It generates a single string which is stored as
// a special block in the Table.
//
// The sequence of calls to FilterBlockBuilder must match the regexp:
// (StartBlock AddKey*)* Finish
class FilterBlockBuilder {
const FilterPolicy* policy_;
std::string keys_; // Flattened key contents
std::vector<size_t> start_; // Starting index in keys_ of each key
std::string result_; // Filter data computed so far
std::vector<Slice> tmp_keys_; // policy_->CreateFilter() argument
std::vector<uint32_t> filter_offsets_;
};
在LevelDB
源碼裡面有很多的Builder
,這些Builder
的主要功能就是實現一個構建器。類似於寫者。
為什麼說是構建器,類似於寫者,而不是寫者呢?主要原因是: - 構建器操作的對象是內存。也就是把要輸出的結果按照想要的格式整理好。在內存中放置好。 - 而Writer主要是把內存裡面的內容寫入到文件裡面。
所以在看代碼的時候,需要注意:BlockBuilder
也好,FilterBlockBuilder
也好,或者是TableBuilder
也好,實際上都是要按內存裡面指定的格式整理在內存裡面。
log_writer
才是真正的把內存裡面的內容寫入到文件裡面。
keys
這裡面有很多內存變數,簡要介紹一下各個變數的作用。
policy_
這裡是指向一個具體的filter_policy
。keys_
裡面包含了所有展開的keys
。並且這些所有的keys
都是存放在一起的。這個可以通過如下代碼推斷出來。
void FilterBlockBuilder::AddKey(const Slice& key) {
Slice k = key;
start_.push_back(keys_.size());
keys_.append(k.data(), k.size());
}
當添加一個key
的時候,start_
裡面會記錄當前這個key
在keys_
裡面的offset
,當然,代碼處理起來比較簡單,只需要start_.push_Back(keys_.size())
就可以了。
而key
真正的內容只需要append
在keys_
裡面就可以了。所以,所有的key
都是存放到keys_
裡面了。
不過需要注意的是:filter block
雖然只有一個,但是實際上filter block
裡面卻是有很多的filter
。每個filter都會利用一系列keys
來生成相應的filter_的輸入
。這裡filter_的輸入
用的是result_
來記錄所有的輸入
。而filter_offsets_
則是將這些隔開,並且更加容易拆分。
filter offset
除此之外,還有兩個變數,
std::vector<Slice> tmp_keys_; // policy_->CreateFilter() argument
std::vector<uint32_t> filter_offsets_;
接下來看一下這兩個變數的含義。這裡需要再次回顧一下SST
文件的格式。
<beginning_of_file>
[data block 1]
[data block 2]
...
[data block N]
[meta block 1]
[meta block index]
[data block index]
[Footer]
<end_of_file>
一般而言,雖然SST
文件裡面聲稱是支持多個meta block
的,但是實際上,也只有一個meta block
。此外,會在每當data block
的大小2K
的時候,開始創建一個filter
。
// See doc/table_format.md for an explanation of the filter block format.
// Generate new filter every 2KB of data
static const size_t kFilterBaseLg = 11;
static const size_t kFilterBase = 1 << kFilterBaseLg;
這裡kFilterBaseLg
表示的意思是對2K
取2
的對數,也就是得到11。那麼真正的大小kFilterBase
這裡也就是2K
。
那麼基於以上原因:
- 只有一個meta block
- 每
2K
創建一個filter。
所以,當給定block_offset
的時候。需要創建的filter
的數目也就確定了。
// 注意:block_offset指的是 filter block的offset.
void FilterBlockBuilder::StartBlock(uint64_t block_offset) {
uint64_t filter_index = (block_offset / kFilterBase);
assert(filter_index >= filter_offsets_.size());
while (filter_index > filter_offsets_.size()) {
GenerateFilter();
}
}
在上面的代碼中,首先第一步是計算出所有的filter
的總數。filter_index
這個變數命名感覺怪怪的。我覺得應該使用filters_number
更加合適。也就是說filters_number = block_offset / kFilterBase
。
而filter_offsets_
裡面則是記錄了當前已經生成的所有的filter
的offset
信息。
所以
while (filter_index > filter_offsets_.size()) {
GenerateFilter();
}
也就容易理解了。當已經生成的filter
的數目小於需要生成的filter
的總數時,那麼就繼續創建filter
。
創建Filter
那麼GenerateFilter
是如何操作的呢?這段代碼相對比較長一些。首先需要看一下result_
變數。result_
變數就是表示的是一個filter
計算之後的輸出。比如boomfilter
經過各種key
計算之後,可能會得到一個filter_Str
。這個filter_str
就是放到result
裡面。
filter_offsets_
裡面的每個元素就是用來記錄每個filter
內容的offset
。
所以result_
與filter_offsets_
的關係,與keys_
和start_
的關係一模一樣。都是表示一段內存區域。然後用vector
來記錄offset的位置。
void FilterBlockBuilder::GenerateFilter() {
// 拿到key的數目
const size_t num_keys = start_.size();
// 如果當前key數目還是0
if (num_keys == 0) {
// 如果key數目為0,這裡應該是表示要新生成一個filter
// 這時應該是重新記錄下offset了。
// Fast path if there are no keys for this filter
filter_offsets_.push_back(result_.size());
return;
}
// Make list of keys from flattened key structure
// start_裡面記錄下offset
start_.push_back(keys_.size()); // Simplify length computation
// 需要多少個key呢?
tmp_keys_.resize(num_keys);
// 依次拿到每個key
for (size_t i = 0; i < num_keys; i++) {
// 這裡拿到每個key的base address.
const char* base = keys_.data() + start_[i];
// 拿到key的長度
size_t length = start_[i+1] - start_[i];
// 生成相應的key,並且放到tmp_keys裡面
tmp_keys_[i] = Slice(base, length);
}
// Generate filter for current set of keys and append to result_.
// 記錄下offset
filter_offsets_.push_back(result_.size());
// 利用tmp_keys生成輸出,並且放到result裡面。
policy_->CreateFilter(&tmp_keys_[0], static_cast<int>(num_keys), &result_);
// 清空keys/start變數
tmp_keys_.clear();
keys_.clear();
start_.clear();
}
Finish
在看Finish
函數之前,需要再看一下Filter block
的結構: