布隆过滤器

242 阅读3分钟

前言

Redis 防止缓存穿透的一种方法,就是设置一个布隆过滤器。阅读了一些博客,这里简单给自己总结一下布隆过滤器的原理。

前提

在计算机编程中个,判断一个元素是否在一个集合中,是一个经常遇到的问题。最直接的办法如下:

  • 遍历集合

这个办法会有一个时间过长,计算量过多的问题。于是,就有了Hash表,即利用空间换时间的概念。

  • Hash 散列表

散列表是可行的,但在绝对庞大的数据下,散列表需要的空间会成为瓶颈。

转机

以上是基于计算机绝对正确的概念去思考解决问题的。但在实际工程中,假如可以收获的绝大的性能优势,一定程度是允许出现误判的。布隆过滤器是解决这类问题的一种有用的解法。

原理

布隆过滤器是利用一个很长的二进制向量和一系列的随机映射函数(F1,F2...F8)来定义的,很长的二进制向量,大概需要散列表的1/8或1/4的空间。假如要判断一个商品Id是否属于我们数据表的中的数据。假设传入的商品Id为:a,商品Id的集合为:ASet = {ai | ai 属于 ASet}

STEP 1 全量加载ASet的数据,初始化过滤器。

  • 1、初始化二进制向量,把所有的 bit 都设置为0;
  • 2、顺序取出 ASet 的一个数据 ai,通过随机映射函数(F1,F2..F8),获得8个信息指纹(f1,f2,...f8),再使用一个随机数产生器G将8个指纹映射到二进制向量的8个bit中,假如发现bit上的数值为1,则不变,假如为0,则设置为1.
  • 3、重复2,直接集合遍历完成。

STEP 2 验证一个值a是否存在集合中

  • 通过随机映射函数(F1,F2..F8),获得8个信息指纹(f1,f2,...f8),再使用一个随机数产生器G将8个指纹映射到二进制向量的8个bit中。假如bit都为1,则认为a值在集合中。
  • 误判:假如STEP 1 的集合数量足够多,而二进制向量的长度比较短,导致二进制的bit位等于1的数量很多,则会一个元素通过映射后,都会落中bit=1的位,引起误判。这个需要在一些参数配置上重新设定一下。通过概率论也可以推导证明。

误判的处理方法

确认误判后,可以另外建一个集合去保留误判的数值,则:这些值不存在数据表中,但布隆过滤器会误判的值。

代码实现:Google Guava

        int total = 100_0000;
        BloomFilter<String> bf = BloomFilter.create(
                Funnels.stringFunnel(Charsets.UTF_8), total, 0.03); # 定制误判率 fpp
        // 初始化
        for (int i = 0; i < total; i++) {
            bf.put("" + i);
        }

        int count = 0;
        for (int i = 0; i < total + 10000; i++) {
            if (bf.mightContain("" + i)) {
                count ++;
            }
        }
        System.out.println("已匹配数量 " + count);
        System.out.println("误判率:" + (count - total)/((total + 10000)*1.0F) * 100);
   
已匹配数量 1000309
误判率:0.03059406

小结

布隆过滤器是解决一个一个元素是否在集合的问题,其不会漏判,只会误判。精确值比Hash要差(基于概率推导是可以定制精确值的,Guava是有现成的工具),但利用的空间会比Hash方法会少。误判时,可以设置一个白名单,直接处理。布隆过滤器常用于一下场景:

  • 1、垃圾邮件判断
  • 2、爬虫网站去重处理
  • 3、缓存防穿透
  • 4、集合重复元素去重

参考