【leveldb】BloomFilter(十九):理论及实现

有关BloomtFilter的理论知识就不详细介绍了,有兴趣的可以点这里BloomFilter理论推导
本篇主要是记录下BloomFilter一些关键知识点并梳理下leveldb中对BloomFilter的应用。

理论

影响BlootFilter准确率的参数有:

  • 哈希函数的个数 k
  • 布隆过滤器位数组容量 m
  • 布隆过滤器插入的数据量 n

为保持准确率得出的结论如下:

  • 当哈希函数个数 k=ln(2)*(m/n) 时查询出错的概率最小。
  • 当不考虑最优哈希函数k时,假设错误率为 ϵ ,在查询错误率不大于 ϵ 的情况下
    m >= n log2(1/є) 才能表示n个元素的集合。
  • 当考虑哈希函数个数最优时同时错误率不能超过 ϵ ,则m >= 1.44*n log2(1/є) 。

举例:
布隆过滤器位数组是m,key数量是n,则每个key消耗的内存是bits_per_key = m / n,
假设查询错误率ϵ,不超过0.01,通过公式m >= 1.44*n log2(1/є) ,可得出 m/n ~= 9.567。也就是说
给每个key分配10个bit位(即bits_per_key = 10)可将错误率控制在0.01。等下leveldb内部实现即可看到其bits_per_key也是默认为10的。

优点
  1. BloomFilter不需要存储元素的实际数据到容器中去一个个比较元素是否存在,只需要对应的位
    来标识元素是否存在,就像上文所描述的,只需要10个bit就可以判断一个key是否存在,这在
    海量数据查询判断时是非常节约空间的。

  2. BloomFilter的时间效率都是常数级别的O(k),k是hash函数个数。由于k个hash函数式相
    互独立,在进行k个hash计算时可以并行计算进一步加速插入、查找等。

缺点

虽然通过多个hash函数来降低hash collision,但仍有可能将不属于这个集合的元素误认为在这个集合中。在leveldb这种允许低错误率的应用场景中还是可以接受的,leveldb在Filter中查找到以后会再次去DataBlock中查找确认的。

参数设定

leveldb中针对BloomFilter的hash函数个数和bits_per_key值设定如下:

1.hash个数
 explicit BloomFilterPolicy(int bits_per_key) : bits_per_key_(bits_per_key) {
    // We intentionally round down to reduce probing cost a little bit
    k_ = static_cast<size_t>(bits_per_key * 0.69);  // 0.69 =~ ln(2)
    if (k_ < 1) k_ = 1;
    if (k_ > 30) k_ = 30;
  }
2.bits_per_key
// Return a new filter policy that uses a bloom filter with approximately
// the specified number of bits per key.  A good value for bits_per_key
// is 10, which yields a filter with ~ 1% false positive rate.
//
// Callers must delete the result after any database that is using the
// result has been closed.
//
// Note: if you are using a custom comparator that ignores some parts
// of the keys being compared, you must not use NewBloomFilterPolicy()
// and must provide your own FilterPolicy that also ignores the
// corresponding parts of the keys.  For example, if the comparator
// ignores trailing spaces, it would be incorrect to use a
// FilterPolicy (like NewBloomFilterPolicy) that does not ignore
// trailing spaces in keys.
LEVELDB_EXPORT const FilterPolicy* NewBloomFilterPolicy(int bits_per_key);

从上面这段注释中我们可知,leveldb的建议设定是bits_per_key = 10,就可以将查询错误率控制在1%了。
当然,用户是可以指定过滤器的,也可以指定BloomFilter的bits_per_key。但是leveldb一般还是建议使用BloomFilter,且bits_per_key = 10就可以了。

源码解析
Bloom.cc

namespace leveldb {

namespace {
//计算哈希值的函数
static uint32_t BloomHash(const Slice& key) {
  return Hash(key.data(), key.size(), 0xbc9f1d34);
}

class BloomFilterPolicy : public FilterPolicy {
 public:
  explicit BloomFilterPolicy(int bits_per_key) : bits_per_key_(bits_per_key) {
    
    //计算哈希函数个数,控制在1~30个范围。
    // We intentionally round down to reduce probing cost a little bit
    k_ = static_cast<size_t>(bits_per_key * 0.69);  // 0.69 =~ ln(2)
    if (k_ < 1) k_ = 1;
    if (k_ > 30) k_ = 30;
  }

  const char* Name() const override { return "leveldb.BuiltinBloomFilter2"; }

  //创建一个BloomFilter数据。传入的值:
  //1、keys: key列表;
  //2、n: key的个数;
  //3、dst: 用于存放BloomFilter的数据地址
  void CreateFilter(const Slice* keys, int n, std::string* dst) const override {
    
   //计算出中的需要的bits个数
    // Compute bloom filter size (in both bits and bytes) 
   size_t bits = n * bits_per_key_;

   //bits太小的话会导致很高的查询错误率,
   //这里强制bits个数不能小于64
    // For small n, we can see a very high false positive rate.  Fix it
    // by enforcing a minimum bloom filter length.
    if (bits < 64) bits = 64;

	//向上按8bit,一个Byte对齐
    size_t bytes = (bits + 7) / 8;
    bits = bytes * 8;

	//扩展下要存储BloomFilter的内存空间,
	//并在尾部一个Byte存哈希函数的个数。
    const size_t init_size = dst->size();
    dst->resize(init_size + bytes, 0);
    dst->push_back(static_cast<char>(k_));  // Remember # of probes in filter

	//接下来开始存储每个key值。
    char* array = &(*dst)[init_size];
    for (int i = 0; i < n; i++) {

	  //BloomFilter理论是通过多个hash计算来减少冲突,
	  //但leveldb实际上并未真正去计算多个hash,而是通过
	  //double-hashing的方式来达到同样的效果。
	  //double-hashing的理论如下:
	  // h(i,k) = (h1(k) + i*h2(k)) % T.size
	  // h1(k) = h, h2(k) = delta, h(i,k) = bitpos
      //1、计算hash值;
	  //2、hash值的高15位,低17位对调
	  //3、按k_个数来存储当前hash值。
      //3-1、计算存储位置;
      //3-2、按bit存;
      //3-3、累加hash值用于下次计算
      // Use double-hashing to generate a sequence of hash values.
      // See analysis in [Kirsch,Mitzenmacher 2006].
      uint32_t h = BloomHash(keys[i]);
      const uint32_t delta = (h >> 17) | (h << 15);  // Rotate right 17 bits
      for (size_t j = 0; j < k_; j++) {
        const uint32_t bitpos = h % bits;
        array[bitpos / 8] |= (1 << (bitpos % 8));
        h += delta;
      }
    }
  }

  //值的匹配就是插入的逆过程了。
  bool KeyMayMatch(const Slice& key, const Slice& bloom_filter) const override {

    //1、插入时按1Byte对齐;
	//2、尾部插入了一个Byte的hash个数
    //所以大小不能下雨2个字节,这样理解不知对否
    const size_t len = bloom_filter.size();
    if (len < 2) return false;

	//除去尾部的1Byte对应的hash个数,就是当前位数组容器的大小
    const char* array = bloom_filter.data();
    const size_t bits = (len - 1) * 8;

    // Use the encoded k so that we can read filters generated by
    // bloom filters created using different parameters.
    const size_t k = array[len - 1];
    if (k > 30) {
      // Reserved for potentially new encodings for short bloom filters.
      // Consider it a match.
      return true;
    }

	//1、计算查询key对应的hash值
	//2、按插入规则去 &,只要有1bit不相同,那就不存在。
    uint32_t h = BloomHash(key);
    const uint32_t delta = (h >> 17) | (h << 15);  // Rotate right 17 bits
    for (size_t j = 0; j < k; j++) {
      const uint32_t bitpos = h % bits;
      if ((array[bitpos / 8] & (1 << (bitpos % 8))) == 0) return false;
      h += delta;
    }
    return true;
  }

 private:
  //每个key需要多少bit来存储表示
  size_t bits_per_key_;
  //hash函数的个数
  size_t k_;
};
}  // namespace

const FilterPolicy* NewBloomFilterPolicy(int bits_per_key) {
  return new BloomFilterPolicy(bits_per_key);
}

}  // namespace leveldb
调用流程
1.创建流程

在这里插入图片描述

2.查询流程

这里只记录了key从TableCache开始流程,上层过程未记录。
在这里插入图片描述

总结

对于节约空间的BloomFilter是非常使用leveldb的,leveldb针对key的很多设计都是尽量减少多余的空间占用。


巨人的肩膀:
https://blog.csdn.net/carbon06/article/details/80118954
https://juejin.im/post/5ecfcc12518825432f59f8ca
https://www.shuzhiduo.com/A/GBJrN9GZJ0/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值