zoukankan      html  css  js  c++  java
  • [转]详解布隆过滤器的原理,使用场景和注意事项

    转自https://zhuanlan.zhihu.com/p/43263751

    今天碰到个业务,他的 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),特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”。

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

    实现原理

    HashMap 的问题

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

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

    布隆过滤器数据结构

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

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

    Ok,我们现在再存一个值 “tencent”,如果哈希函数返回 3、4、8 的话,图继续变为:

    值得注意的是,4 这个 bit 位由于两个值的哈希函数都返回了这个 bit 位,因此它被覆盖了。现在我们如果想查询 “dianping” 这个值是否存在,哈希函数返回了 1、5、8三个值,结果我们发现 5 这个 bit 位上的值为 0,说明没有任何一个值映射到这个 bit 位上,因此我们可以很确定地说 “dianping” 这个值不存在。而当我们需要查询 “baidu” 这个值是否存在的话,那么哈希函数必然会返回 1、4、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 的速度越快,且布隆过滤器的效率越低;但是如果太少的话,那我们的误报率会变高。

    k 为哈希函数个数,m 为布隆过滤器长度,n 为插入的元素个数,p 为误报率

    如何选择适合业务的 k 和 m 值呢,这里直接贴一个公式:

    如何推导这个公式这里只是提一句,因为对于使用来说并没有太大的意义,你让一个高中生来推会推得很快。k 次哈希函数某一 bit 位未被置为 1 的概率为:

    [公式]

    插入n个元素后依旧为 0 的概率和为 1 的概率分别是:

    [公式] [公式]

    标明某个元素是否在集合中所需的 k 个位置都按照如上的方法设置为 1,但是该方法可能会使算法错误的认为某一原本不在集合中的元素却被检测为在该集合中(False Positives),该概率由以下公式确定

    [公式]

    最佳实践

    常见的适用常见有,利用布隆过滤器减少磁盘 IO 或者网络请求,因为一旦一个值必定不存在的话,我们可以不用进行后续昂贵的查询请求。

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

    大Value拆分

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

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

    参考资料

    https://hackernoon.com/probabilistic-data-structures-bloom-filter-5374112a7832
    https://www.jasondavies.com/bloomfilter/

    Comments

    • li ming
      li ming10 个月前
      哈希函数数量选择,总bit数量选择,命中率期望都没说啊
    • li ming
      li ming10 个月前
      删除可以用计数法实现
    • Young Chen
      Young Chen (作者) 回复li ming10 个月前
      因为上班所以还有部分没写完...我也是在边学边写,你说的部分可以先看看参考资料
    • Young Chen
      Young Chen (作者) 回复li ming10 个月前

      计数法其实就不是单纯的 bit 向量了,而是要存储数值了。布隆过滤器本身的设计还是希望使用简单,加速判断的。

      • 知乎用户知乎用户9 个月前
        bf 的设计关键感觉还是预设容量满了时候的处理方案。。
      • Young Chen
        Young Chen (作者) 回复yetingsky9 个月前

        这个能详细说说么,bf 的应用场景我还真不是很清楚

      • yetingsky
        yetingsky回复Young Chen (作者)9 个月前

        bf 不可避免会碰到超过预期容量的情况,比如之前是按照1000个元素设计的,当超过 1000 个后 bf 的准确率就会下降。

        一般的解决思路是两个,一是存储原始数据,当 bf 超过 1000 个元素后生成一个 2000 个元素的 bf,另一种是堆叠 bf (叫做 scalable bloomfilter),超过 1000 个元素后再生成一个新的 1000 容量的 bf,查询的时候查多个。

      • Young Chen
        Young Chen (作者) 回复yetingsky9 个月前
        非常感谢,学到了,我把这部分内容补充一下
        (。・▽・。)ρ
      • bingo
        bingo回复yetingsky7 个月前

        那是不是意味着需要有个watcher观察bitmap的储存情况?这样似乎也比较浪费性能吧

     
    • ikzjfr0
      ikzjfr05 个月前

      关于布隆过滤器的使用场景。在非redis下非常适合,就如题主所举例的。但是在redis下。即便是1亿个key-value的存储,get一个key value也是非常快的。我不明白在redis下的实际意义。

    • 杜璞
      杜璞回复ikzjfr02 个月前

      省空间

    • keguang
      keguang4 个月前

      计数法,该位置可能有多个元素,将对应值减一,那么删除的到底是哪一个元素呢?

    • 金尼尔斯
      金尼尔斯2 个月前

      查询时的判断稍微不清晰。只举了全为0和全为1的例子。若既有0又有1呢。也是不存在吧

    • 知乎用户知乎用户16 天前
      哈希函数用两个就够了,可以参考哈佛的一篇论文。
  • 相关阅读:
    ASP.NET MVC随想录——锋利的KATANA
    ASP.NET MVC随想录——漫谈OWIN
    Notepad++ 64位 插件管理
    C#实现按键精灵的'找图' '找色' '找字'的功能
    http://www.haolizi.net/example/view_2380.html
    C# 关于在原图中寻找子图片坐标的类
    WebBrowser控件默认使用IE9,IE10的方法
    Springboot---后台导出功能,easyExcel
    vue---EleElement UI 表格导出功能
    vue---提取公共方法
  • 原文地址:https://www.cnblogs.com/makai/p/11187910.html
Copyright © 2011-2022 走看看