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
其中BloomFilterPolicy
为FilterPolicy
的派生类,所以我们先来看`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
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。