leveldb源码分析(2)-bloom filter

bloom filter in leveldb

引言

bloom filter是一种用于快速判断某个元素是否属于集合的多哈希映射查找算法,但是并不要求100%正确。典型的应用场合有垃圾邮件过滤,http缓存中判断一条url是否在现有的url集合中等。

本文着重于分析bloom filter在leveldb中的实现和应用,不会涉及太多关于bloom filter本身的原理,性能分析。

源码分析

bloom filter的实现涉及到3个文件,include/leveldb/filter_policy.h,util/filter_policy.cc,util/bloom.cc其中BloomFilterPolicyFilterPolicy的派生类,所以我们先来看`filter_policy的实现。

class FilterPolicy {
 public:
  virtual ~FilterPolicy();//析构函数
  virtual const char* Name() const = 0;//返回该policy的名字,若编码改变,则名字必须改变
  virtual void CreateFilter(const Slice* keys, int n, std::string* dst)
      const = 0;
//keys包含了一串key,(可能重复),按照用户定制的比较器排序
//该函数根据这串key创建一个过滤器dst,(字符串形式)
  virtual bool KeyMayMatch(const Slice& key, const Slice& filter) const = 0;
//判断key是否包含在filter中,大部分情况下应该返回false,即随机一个key,在filter中命中的概率很小。
};
extern const FilterPolicy* NewBloomFilterPolicy(int bits_per_key);
//使用bloom filter构建一个filterPolicy,使用者必须在某个数据库使用完该结果关闭后删除该结果。若你使用的比较器忽略了key的某些部分,那么你必须自行实现一个FilterPolicy

上述的FilterPolicy只是一个基类接口,我们可以按照该接口实现自己的filter policy。下面的BloomFilter类才是具体的实现。

private:
  size_t bits_per_key_;//每个key所占的bit(粗略),将按照该值分配filter大小
  size_t k_;//每个key散列到数组中的bit数,略小于bits_per_key_
public:
  explicit BloomFilterPolicy(int bits_per_key)
      : bits_per_key_(bits_per_key) {
    k_ = static_cast<size_t>(bits_per_key * 0.69);  // 0.69 =~ ln(2)
    if (k_ < 1) k_ = 1;
    if (k_ > 30) k_ = 30;
  }
virtual const char* Name() const {
    return "leveldb.BuiltinBloomFilter2";
  }



  virtual void CreateFilter(const Slice* keys, int n, std::string* dst) const {
    //计算filter的大小,n为key的数量
    size_t bits = n * bits_per_key_;

    // 限制最小值
    if (bits < 64) bits = 64;
    //计算所需bite大小
    size_t bytes = (bits + 7) / 8;
    bits = bytes * 8;

    const size_t init_size = dst->size();
    dst->resize(init_size + bytes, 0);
    dst->push_back(static_cast<char>(k_));
    char* array = &(*dst)[init_size];
    //先偏移到init_size的位置再寻址,[]的运算优先级比&大
    for (size_t i = 0; i < n; i++) {
      //利用hash算法生成一系列hash值,32bit
      uint32_t h = BloomHash(keys[i]);
      const uint32_t delta = (h >> 17) | (h << 15);  // 向右旋转17个bit,该实现非常精髓地利用了位运算,你们自己感受一下-_-
      for (size_t j = 0; j < k_; j++) {
        const uint32_t bitpos = h % bits;
        array[bitpos/8] |= (1 << (bitpos % 8));
        h += delta;
      }
      //由于用的是char数组而不是bit数组,所以赋值到相应的bit有些麻烦,需要先寻址到相应byte然后做位运算。
      //最后的h+=delta以及为什么需要向右旋转17bit跟hash算法的实现有关,详情请搜索double hashing
    }
  }

KeyMayMatch的实现与CreateFilter的实现类似,给定一个key和一个filter判断key是否在filter中,不再赘述。

double hashing的公式为

$$
h(i,k)= (h_1(k)+i*h_2(k)) mod |T|
$$

可以把BloomHash当做h1,向右旋转17个bit当做h2。

k_值的选择是有依据的,若k_完全等于bit_per_key_,那么filter会非常拥挤,充满了1,我们知道filter中1的数量越少 越好,否则随机一个key也能命中filter,但k_太小的话,一个随机key通过hash后只需命中k_(少量)个位置即可命中。有兴趣的读者可以自行查阅文献,以下文献详细证明了k的选择在什么时候是误差最小的:bloom filter in math

总结

bloom filter的实现比较简单,没有多少复杂的逻辑和技巧,对于参数的选择有兴趣的读者可以自行试验或者参考相关文献。

to be continued

@SeeYouSpaceCowboy


redteam
c++/scala/python 相关技术

亚洲倒数第二程序员

101 声望
7 粉丝
0 条评论
推荐阅读
scala中:: , +:, :+, :::, +++的区别
:: 该方法被称为cons,意为构造,向队列的头部追加数据,创造新的列表。用法为 x::list,其中x为加入到头部的元素,无论x是列表与否,它都只将成为新生成列表的第一个元素,也就是说新生成的列表长度为list的长度...

Jefffrey2阅读 25.3k

又一款眼前一亮的Linux终端工具!
今天给大家介绍一款最近发现的功能十分强大,颜值非常高的一款终端工具。这个神器我是在其他公众号文章上看到的,但他们都没把它的强大之处介绍明白,所以我自己体验一波后,再向大家分享自己的体验。

良许5阅读 1.8k

Linux终端居然也可以做文件浏览器?
大家好,我是良许。在抖音上做直播已经整整 5 个月了,我很自豪我一路坚持到了现在【笑脸】最近我在做直播的时候,也开始学习鱼皮大佬,直播写代码。当然我不懂 Java 后端,因此就写写自己擅长的 Shell 脚本。但...

良许1阅读 2.1k

C++编译器和链接器的完全指南
C++是一种强类型语言,它的编译和链接是程序开发过程中不可或缺的两个环节。编译器和链接器是两个非常重要的概念。本文将详细介绍C++中的编译器和链接器以及它们的工作原理和使用方法。

小万哥2阅读 1k

封面图
比cat更好用的命令!
但 cat 命令两个很重大的缺陷:1. 不能语法高亮输出;2. 文本太长的话无法翻页输出。正是这两个不足,使得 cat 只能用来查看行数不多的小文件。

良许2阅读 997

完了,良许直播中删库了……
大家好,我是良许。今天跟大家聊个尴尬的事,大家可以本着看热闹不嫌事大的心态来听我唠唠。经常来我直播间(视频号+抖音)的小伙伴都知道,我最近一直都在直播间手把手现场写 Shell 脚本。就在前天晚上,我写 Sh...

良许1阅读 1.3k

DBoS 系统说明
程序员TianSong以单片机开发入门,后续又做了 Qt 相关工作,有时间后开始进行 linux 相关的学习,恰巧在二一年十一月份,百问网的韦东山老师进行了三个月的 linux 驱动直播,于是有了开发 DBoS 的念头。

TianSong1阅读 1.4k

亚洲倒数第二程序员

101 声望
7 粉丝
宣传栏