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


Jefffrey
101 声望7 粉丝

亚洲倒数第二程序员