布隆过滤器
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
整个的写入流程
- 从
DataBlock
中获得 Key =====>AddKey(const Slice& key)
, 将所有的 key 的数据都放在了 keys 中,因为是按照string
来存储的,所以用start
标识每个key
的位置- 将
key
都保存下来之后我们就要开始调用StartBlock(uint64_t offset)
, 传入参数offset
也就是data block 的偏移量,我们就能知道处理了多少数据,用于计算 filter 的数目(每2KB创建一个filter)- 具体的调用
GenerateFilter()
, 按照规格 (2KB)调用创建 布隆过滤器的函数- 最终调用
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_);
}