leveldb-FilterBlock实现

布隆过滤器

leveldb的写入的策略采用了 LSM 树🌲,加上很多的优化,写入的性能很强悍。

但是读取的性能是稍微差了一点,可能出现 读放大 的情况,为了进行优化,避免很多的不必要的磁盘IO,使用布隆过滤器 快速判断数据是不是处于某个SSTable

常用的判断某个数据是否是处于集合中我们直接用 set Or hash就好了,但是这 必须要我们保存所有的数据,这在判断数据库的情况是肯定不行的,解决的办法就是使用 布隆过滤器

bitmap进行判断,使用K个哈希函数将key进行映射到上面,那么我们需要判断 key 是否是处于这个集合中,再次用 k 个哈希函数进行映射。

  • 如果说映射到的 bitmap 中为0, 那么 数据一定不在集合中
  • 如果说都是1的话, 可能是在集合中

如果说 n 的大小实在是太小了,那么 假阳性的情况可能是比较严重的

Filter

const FilterPolicy* policy_;    /* filter 类型,如 BloomFilterPolicy */
    std::string keys_;              /* User Keys,全部塞到一个 string 中 */
    std::vector<size_t> start_;     /* 每一个 User Key 在 keys_ 中的起始位置 */
    std::string result_;            /* keys_ 通过 policy_ 计算出来的 filtered data */
    std::vector<Slice> tmp_keys_;   /* policy_->CreateFilter() 的参数 */
    
    /* filter 在 result_ 中的位置,filter_offsets_.size() 就是 Bloom Filter 的数量 */
    std::vector<uint32_t> filter_offsets_;  
};

FilterBlock

整个的写入流程

  1. DataBlock 中获得 Key =====> AddKey(const Slice& key), 将所有的 key 的数据都放在了 keys 中,因为是按照 string 来存储的,所以用 start 标识每个 key 的位置
  2. key 都保存下来之后我们就要开始调用 StartBlock(uint64_t offset), 传入参数 offset 也就是data block 的偏移量,我们就能知道处理了多少数据,用于计算 filter 的数目(每2KB创建一个filter)
  3. 具体的调用 GenerateFilter(), 按照规格 (2KB)调用创建 布隆过滤器的函数
  4. 最终调用Finish完成整个的构建,将最终的一些 metadata 写到最终的数据中
#include "filterBlock.h"
#include "Filter.h"

#include "Coding.h"

using namespace xindb;

// DataBlock 中每 2KB 就生成一个布隆过滤器
static const size_t kFilterBasebit = 11;
static const size_t kFilterBase    = 1 << kFilterBasebit;  


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


// 使用 ADD 将数据写入之后开始创建 filterblock, 需要传入 datablock 的偏移量来计算 filter 的数目
void FilterBlockBuilder::StartBlock(uint64_t block_offset) {
    uint64_t filter_index = (block_offset / kFilterBase);       // 需要创建的 filter 的数目
    assert(filter_index >= filter_offsets_.size());

    // filter_offset 保存每个 bloom filter 的起始偏移量
    while (filter_index > filter_offsets_.size()) {
        GenerateFilter();
    }
}


// 将 InternalKey 写入到 keys_ 中, 用 start_ 标识 key 的位置
// 将全部的数据写入完毕之后会调用StartBlock 
void FilterBlockBuilder::AddKey(const Slice& key) {
    Slice k = key;
    start_.push_back(keys_.size());        // 新插入的key的起始位置
    keys_.append(k.data(), k.size());       // 直接append到keys中去
}


// 生成Filter, 每 2KB 生成一个 bloom 
void FilterBlockBuilder::GenerateFilter() {
    // 获得key,放到tmp_keys中
    const size_t num_keys = start_.size();

    if (num_keys == 0) {
        filter_offsets_.push_back(result_.size());
        return ;
    }

    start_.push_back(keys_.size());

    tmp_keys_.resize(num_keys);                 // 作为参数传入到生成 Filter 的函数


    // 将keys中的 InternalKey 取出来
    for (size_t i = 0; i < num_keys; i++) {
        const char* base = keys_.data() + start_[i];
        size_t len = start_[i+1] - start_[i];
        tmp_keys_[i] = Slice(base, len);
    }

    // 记录 Bloom Filter 结果的偏移量
    filter_offsets_.push_back(result_.size());

    policy_->CreateFilter(&tmp_keys_[0], static_cast<int>(num_keys), &result_);

    tmp_keys_.clear();
    keys_.clear();
    start_.clear();
}


// 完成最终的 MetaData 的写入,供读取的时候能够快速的定位
Slice FilterBlockBuilder::Finish() {
    if (!start_.empty()) {
        GenerateFilter();
    }

    // 将所有的偏移量写入到 result_ 的尾部
    size_t Size = result_.size();
    for (size_t i = 0; i < result_.size(); i++) {
        PutFixed32(&result_, filter_offsets_[i]);
    }

    // 将过滤器的个数扔到尾部
    PutFixed32(&result_, Size);

    // 将Base也就是我们每2KB生成一个过滤器写入
    result_.push_back(kFilterBase);
    return Slice(result_);
}



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值