大白话解析LevelDB: FilterBlockBuilder

FilterBlockBuilder

与 BlockBuilder 类似,FilterBlockBuilder 是 FilterBlock 的构建器。

老规矩,先看下 FilterBlockBuilder 的定义,对外提供了哪些接口。

在构造 SST 的过程中,每当一个 Data Block 构建完成,就会通过StartBlock(uint64_t block_offset)方法在 Filter Block 里构建与下一个 Data Block 对应的 Filte。

通过Add(const Slice& key)方法,我们可以将需要 Key 添加到对应的 Filter 里。

最后再通过Finish()方法,获取 FilterBlock 的完整内容。

class FilterBlockBuilder {
   public:
    explicit FilterBlockBuilder(const FilterPolicy*);

    FilterBlockBuilder(const FilterBlockBuilder&) = delete;
    FilterBlockBuilder& operator=(const FilterBlockBuilder&) = delete;

    // 名字有点误导性,真实职责是构造与 Data Block 对应的 Filters。
    // block_offset 是对应的 Data Block 的起始地址。
    void StartBlock(uint64_t block_offset);

    // 将 Key 添加到 filter 里。
    void AddKey(const Slice& key);

    // 结束 Filter Block 的构建,并返回 Filter Block 的完整内容
    Slice Finish();
};

FilterBlockBuilder 的代码实现

FilterBlockBuilder::AddKey(const Slice& key)

AddKey的作用是将 Key 添加到 filter buffer 里。

std::string FilterBlockBuilder::keys_里存放的是所有的 key,是一个所有 key 拼接起来的字符串。

std::vector<size_t> FilterBlockBuilder::start_里存储的是每个 key 在 keys_ 中的位置。

AddKey就是将 key 添加到 keys_ 里,并且将 key 在 keys_ 中的位置添加到 start_ 里。

void FilterBlockBuilder::AddKey(const Slice& key) {
    Slice k = key;
    // std::vector<size_t> start_ 里存储的是每个 key 在 keys_ 中的位置。
    start_.push_back(keys_.size());
    // std::string keys_ 里存放的是所有的 key,是一个所有 key 拼接起来的字符串。
    keys_.append(k.data(), k.size());
}

FilterBlockBuilder::FilterBlockBuilder(const FilterPolicy*)

先来看下 FilterBlockBuilder 的构造函数,它接收一个 FilterPolicy*,这个 FilterPolicy* 是一个接口,它定义了 Filter 的一些操作,比如CreateFilter()KeyMayMatch()等。 关于 FilterPolicy 的详情,感兴趣的同学可以移步大白话解析LevelDB: FilterPolicy

FilterBlockBuilder 的构造函数啥也没做,只是将 FilterPolicy* 保存到成员变量里,供后续使用。

FilterBlockBuilder::FilterBlockBuilder(const FilterPolicy* policy) : policy_(policy) {}

FilterBlockBuilder::StartBlock(uint64_t block_offset)

block_offset 指的是对应的 Data Block 的起始地址。

FilterBlockBuilder::StartBlock用于为下一个 Data Block 构建新的 Filter。

FilterBlockBuilder 里有个 buffer,用于积攒需要添加到 Filter 里的 Key。

FilterBlockBuilder::StartBlock的职责是把之前积攒的 Key 都构造成 Filter(因为这些 Key 对应的是之前的 Data Block),然后清空 buffer,为下一个 Data Block 构建新的 Filter。

void FilterBlockBuilder::StartBlock(uint64_t block_offset) {
    // 每个 Filter 的大小是固定的,kFilterBase,默认为 2KB。
    // Filter 与 Data Block 是分组对应的关系。
    // filter_index 用于记录当前 Data Block 对应的 Filter 的索引。
    // 比如,Data Block 的大小是 4KB,那么:
    //   - Data Block 0(block_offset = 0) 对应 Filter 0
    //   - Data Block 1(block_offset = 4KB) 对应 Filter 2
    //   - Data Block 2(block_offset = 8KB) 对应 Filter 4
    // Filter 1, 3, 5 是空的,不对应任何 Data Block。
    uint64_t filter_index = (block_offset / kFilterBase);

    assert(filter_index >= filter_offsets_.size());

    // filter_offsets_ 中记录了每个 Filter 的起始偏移量。
    // 换句话说,filter_offsets_.size() 就是已经构造好的 Filter 数量。
    // 在记录新的 Key 之前,需要先把 buffer 里积攒的 Key (属于上一个 Data Block 的 Key)都构造成 Filter。
    // 这里的 while 是上一个 Data Block 构建 Filter,然后清空 filter buffer,为新的 Data Block 做准备。
    // 这里的 while 理解起来会有点误解,看上去好像可能会为了 1 个 Data Block 构建多个 Filter 的样子,其实不是。
    // 假设 Data Block 大小为 4KB, Filter 大小为 2KB,那么 Filter 0 对应 Data Block 0,Filter 1 是个空壳,不对应任何 Data Block。
    // 假设 Data Block 的大小为 1KB,Filter 的大小为 2KB,那么就会有 1 个 Filter 对应 2 个 Data Block。
    while (filter_index > filter_offsets_.size()) {
        GenerateFilter();
    }
}

FilterBlockBuilder::GenerateFilter()

GenerateFilter()的作用是根据 filter buffer 生成一个 filter,将该 filter 压入到 result_中,并更新 filter_offsets。

如果 filter buffer 里没有任何 Key,那么就只是往 filter_offsets_ 里压入一个位置,这个位置指向上一个 filter 的位置,不往 result_ 里压入任何 filter。

相当于构造了一个空的 dummy filter。

GenerateFilter的流程简化如下:

  1. 获取 filter buffer 里 key 的数量
  2. 如果 key 的数量为 0,那就构造一个空的 dummy filter,结束。
  3. key 的数量不为 0,继续往下走。
  4. 取出所有的 key,构造出一个 filter,压入到 result_ 中。
  5. 清空 filter buffer,为下一个 filter 做准备。

实现如下:

// 生成一个 filter,然后压入到 result_ 中
void FilterBlockBuilder::GenerateFilter() {
    // std::vector<size_t> start_ 里存储的是每个 key 在 std::string keys_ 中的位置。
    // start_.size() 代表的是 keys_ 缓冲区中的 key 的数量。
    const size_t num_keys = start_.size();

    // std::string result_ 里存放的是每个 filter 拍平后的数据,也就是说,
    // filter-0, filter-1, ..., filter-n 是按顺序连续存放到 result_ 里的。
    // 
    // std::vector<uint32_t> filter_offsets_ 里则存放每个 filter 在 result_ 里的位置,
    // 比如 filter_offsets[0] 代表的是 filter-0 在 result_ 里的位置。
    // 
    // 此处如果 num_keys 为 0,则构造一个空的 dummy filter,但是将该 filter 的
    // 位置 filter_offsets_[i] 记录为上一个 filter 的位置。
    // 比如 filter_offsets[1] = filter_offsets[0],filter-0 是有实际数据的,
    // 但 filter-1 是个空的 dummy filter,就把 filter_offsets[1] 指向 filter-0。
    // 这主要用于 Data Block 的大于 Filter 大小的情况,参考 FilterBlockBuilder::StartBlock
    // 中的注释可更好理解。
    if (num_keys == 0) {
        // Fast path if there are no keys for this filter
        // 只是往 filter_offsets_ 里压入一个位置,但是没有往 result_ 里压入 filter。
        filter_offsets_.push_back(result_.size());
        return;
    }

    // 此处往 start_ 里压入下一个 key 起始位置是为了方便计算 keys_ 中最后一个 key 的长度。
    // keys_[i] 的长度计算方式为 start_[i+1] - start_[i]。
    // 假设 keys_ 里一共有 n 个 key,那么 start_ 里一共有 n 个元素,如果我们要计算
    // 最后一个 key 的长度,则为 start_[n] - start[n-1],但是 start_[n] 并不存在,越界了。
    // 所以这里先压入一个 start_[n],就可以计算出最后一个 key 的长度了。
    // 反正 GenerateFilter() 结束后 start_ 就会被清空重置了,所以这里修改 start_ 没有问题。
    start_.push_back(keys_.size());  

    /* tmp_keys_ 主要的作用就是作为 CreateFilter() 方法参数构建 Bloom Filter */
    // tmp_keys_ 只是一个临时的富足
    tmp_keys_.resize(num_keys);

    // 从 keys_ 中取出所有的 key,放到 std::vector<Slice> tmp_keys_ 中。
    // 不太懂为什么 leveldb 不直接把 keys_ 设为 std::vector<Slice> 类型 = =。
    for (size_t i = 0; i < num_keys; i++) {
        // 取 keys_[i] 的起始地址
        const char* base = keys_.data() + start_[i]; 
        // 计算 keys[i] 的长度,
        // 现在可以看到上面 start_.push_back(keys_.size()) 的作用了
        size_t length = start_[i + 1] - start_[i];   
        // 把 keys_[i] 放到 tmp_keys_[i] 中
        tmp_keys_[i] = Slice(base, length);          
    }

    // 先把新 filter 的位置记录到 filter_offsets_ 中
    filter_offsets_.push_back(result_.size());
    // 再构造新 filter,然后压入到 result_ 中
    policy_->CreateFilter(&tmp_keys_[0], static_cast<int>(num_keys), &result_);

    // 新 filter 构造完毕,把缓冲区清空,为下一个 filter 做准备。
    tmp_keys_.clear();
    keys_.clear();
    start_.clear();
}

细心的同学可能会有疑惑,FilterBlockBuilder::tmp_keys_只是在 FilterBlockBuilder::GenerateFilter()这一个方法中有被使用到,其作用只是一个临时的局部变量而已,FilterBlockBuilder为什么要把tmp_keys_设为成员变量?

其实是为了性能。tmp_keys_std::vector类型,如果设为局部变量,那么每次调用GenerateFilter()时都需要重新创建tmp_keys并且重新分配内存。

假设每次调用FilterBlockBuilder::GenerateFilter()时使用到的tmp_keys_的内存空间为 4KB,那每次调用GenerateFilter()都需要重新创建tmp_keys_,并重新分配 4KB 的内存。

而如果将tmp_keys_设为成员变量,那么只需要在第一次调用GenerateFilter()时创建tmp_keys_,并分配 4KB 的内存,这 4KB 的内存会一直保留,直到FilterBlockerBuilder对象销毁。

FilterBlockBuilder::Finish()

FilterBlockBuilder::Finish() 会将 filter buffer 里的剩余数据构造成最后一个 filter,然后返回 FilterBlock 的完整内容。

Filter Block 的完整内容: result_ + filter_offsets_ + array_offset + kFilterBaseLg

其中:

  • result是所有 filter 拍平后的数据,也就是说,filter-0, filter-1, …, filter-n 是按顺序连续存放到 result_ 里的。
  • filter_offsets里存放每个 filter 在 result_ 里的位置,比如 filter_offsets[0] 代表的是 filter-0 在 result_ 里的位置。
  • array_offset是 filter_offsets_ 的位置。
  • kFilterBaseLg是 filter 的大小。
Slice FilterBlockBuilder::Finish() {
    // start_ 非空,表示 filter buffer 里还有数据,需要把它们构造成 Filter。
    if (!start_.empty()) {
        GenerateFilter();
    }

    const uint32_t array_offset = result_.size();
    // result_ 需要与 filter_offsets_ 搭配食用,
    // 把 filter_offsets_ 中每个 filter 的位置压入到 result_ 里。
    for (size_t i = 0; i < filter_offsets_.size(); i++) {
        PutFixed32(&result_, filter_offsets_[i]);
    }

    // 再把 filter_offsets_ 的位置压入到 result_ 里。
    PutFixed32(&result_, array_offset);
    // 最后再把 filter 的大小放入到 result_ 里,这样 filter 才能被正确解码
    result_.push_back(kFilterBaseLg);  // Save encoding parameter in result

    // 返回 result_:
    // result = result_ + filter_offsets_ + array_offset + kFilterBaseLg
    return Slice(result_);
}

Filter Block 的内容格式

Filter Block 的内容由以下几部分组成:

  • Filter Data:这部分包含了所有的 Filter 数据,每个 Filter 数据都是由 FilterPolicy::CreateFilter() 方法生成的。这些 Filter 数据在 Filter Block 中是连续存放的。

  • Filter Offsets:这部分是一个数组,存放了每个 Filter 在 Filter Data 中的起始位置。这个数组的每个元素都是一个 4 字节的整数,表示对应的 Filter 在 Filter Data 中的起始位置。

  • Start of Filter Offsets:这部分是一个 4 字节的整数,表示 Filter Offsets 在 Filter Block 中的起始位置。

  • Filter Size:这部分是一个 1 字节的整数,表示 Filter 的大小。

以下是 Filter Block 的内容格式的图示:

+-------------------+-------------------+-------------------------+-------------+
|   Filter Data     |  Filter Offsets   | Start of Filter Offsets | Filter Size |
+-------------------+-------------------+-------------------------+-------------+

关于 Filter Block 内容格式的更多细节,可以移步参考Filter Block 的内容格式

  • 21
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值