详解布隆过滤器的原理、使用场景和注意事项

今天碰到个业务,他的 Redis 集群有个大 Value 用途是做为布隆过滤器,但沟通的时候被小怼了一下,意思大概是 “布隆过滤器原理都不懂,还要我优化?”。技术菜被人怼认了、怪不得别人,本身以前确实只是据说过这个,可是没深刻了解过,趁这个机会补充一下知识。数组

在进入正文以前,以前看到的有句话我以为说得很好:服务器

Data structures are nothing different. They are like the bookshelves of your application where you can organize your data. Different data structures will give you different facility and benefits. To properly use the power and accessibility of the data structures you need to know the trade-offs of using one.网络

大意是不一样的数据结构有不一样的适用场景和优缺点,你须要仔细权衡本身的需求以后妥善适用它们,布隆过滤器就是践行这句话的表明。数据结构

什么是布隆过滤器

本质上布隆过滤器是一种数据结构,比较巧妙的几率型数据结构(probabilistic data structure),特色是高效地插入和查询,能够用来告诉你 “某样东西必定不存在或者可能存在”。app

相比于传统的 List、Set、Map 等数据结构,它更高效、占用空间更少,可是缺点是其返回的结果是几率性的,而不是确切的。函数

实现原理

HashMap 的问题

讲述布隆过滤器的原理以前,咱们先思考一下,一般你判断某个元素是否存在用的是什么?应该蛮多人回答 HashMap 吧,确实能够将值映射到 HashMap 的 Key,而后能够在 O(1) 的时间复杂度内返回结果,效率奇高。可是 HashMap 的实现也有缺点,例如存储容量占比高,考虑到负载因子的存在,一般空间是不能被用满的,而一旦你的值不少例如上亿的时候,那 HashMap 占据的内存大小就变得很可观了。性能

还好比说你的数据集存储在远程服务器上,本地服务接受输入,而数据集很是大不可能一次性读进内存构建 HashMap 的时候,也会存在问题。优化

布隆过滤器数据结构

布隆过滤器是一个 bit 向量或者说 bit 数组,长这样:orm


 
image

若是咱们要映射一个值到布隆过滤器中,咱们须要使用多个不一样的哈希函数生成多个哈希值,并对每一个生成的哈希值指向的 bit 位置 1,例如针对值 “baidu” 和三个不一样的哈希函数分别生成了哈希值 一、四、7,则上图转变为:索引


 
image

Ok,咱们如今再存一个值 “tencent”,若是哈希函数返回 三、四、8 的话,图继续变为:


 
image

值得注意的是,4 这个 bit 位因为两个值的哈希函数都返回了这个 bit 位,所以它被覆盖了。如今咱们若是想查询 “dianping” 这个值是否存在,哈希函数返回了 一、五、8三个值,结果咱们发现 5 这个 bit 位上的值为 0,说明没有任何一个值映射到这个 bit 位上,所以咱们能够很肯定地说 “dianping” 这个值不存在。而当咱们须要查询 “baidu” 这个值是否存在的话,那么哈希函数必然会返回 一、四、7,而后咱们检查发现这三个 bit 位上的值均为 1,那么咱们能够说 “baidu” 存在了么?答案是不能够,只能是 “baidu” 这个值可能存在。

这是为何呢?答案跟简单,由于随着增长的值愈来愈多,被置为 1 的 bit 位也会愈来愈多,这样某个值 “taobao” 即便没有被存储过,可是万一哈希函数返回的三个 bit 位都被其余值置位了 1 ,那么程序仍是会判断 “taobao” 这个值存在。

支持删除么

目前咱们知道布隆过滤器能够支持 add 和 isExist 操做,那么 delete 操做能够么,答案是不能够,例如上图中的 bit 位 4 被两个值共同覆盖的话,一旦你删除其中一个值例如 “tencent” 而将其置位 0,那么下次判断另外一个值例如 “baidu” 是否存在的话,会直接返回 false,而实际上你并无删除它。

如何解决这个问题,答案是计数删除。可是计数删除须要存储一个数值,而不是原先的 bit 位,会增大占用的内存大小。这样的话,增长一个值就是将对应索引槽上存储的值加一,删除则是减一,判断是否存在则是看值是否大于0。

如何选择哈希函数个数和布隆过滤器长度

很显然,太小的布隆过滤器很快全部的 bit 位均为 1,那么查询任何值都会返回“可能存在”,起不到过滤的目的了。布隆过滤器的长度会直接影响误报率,布隆过滤器越长其误报率越小。

另外,哈希函数的个数也须要权衡,个数越多则布隆过滤器 bit 位置位 1 的速度越快,且布隆过滤器的效率越低;可是若是太少的话,那咱们的误报率会变高。


 
image

k 为哈希函数个数,m 为布隆过滤器长度,n 为插入的元素个数,p 为误报率。
至于如何推导这个公式,我在知乎发布的文章有涉及,感兴趣能够看看,不感兴趣的话记住上面这个公式就好了。

最佳实践

常见的适用常见有,利用布隆过滤器减小磁盘 IO 或者网络请求,由于一旦一个值一定不存在的话,咱们能够不用进行后续昂贵的查询请求。

另外,既然你使用布隆过滤器来加速查找和判断是否存在,那么性能很低的哈希函数不是个好选择,推荐 MurmurHash、Fnv 这些。

大Value拆分

Redis 因其支持 setbit 和 getbit 操做,且纯内存性能高等特色,所以自然就能够做为布隆过滤器来使用。可是布隆过滤器的不当使用极易产生大 Value,增长 Redis 阻塞风险,所以生成环境中建议对体积庞大的布隆过滤器进行拆分。

拆分的形式方法多种多样,可是本质是不要将 Hash(Key) 以后的请求分散在多个节点的多个小 bitmap 上,而是应该拆分红多个小 bitmap 以后,对一个 Key 的全部哈希函数都落在这一个小 bitmap 上。

参考资料

做者:YoungChen__ 连接:https://www.jianshu.com/p/2104d11ee0a2 来源:简书 简书著做权归做者全部,任何形式的转载都请联系做者得到受权并注明出处。
相关文章
相关标签/搜索