布隆过滤器及其应用

什么是布隆过滤器?

布隆过滤器是一种数据结构,具有快速插入和查找的特性,能确定某个字符串一定存在或者可能存在。布隆过滤器有着高效的空间利用率,它不存储具体数据,只存储数据的关键标识,所以占用的空间较小。它的查询结果可能会存在一定误差,但是误差总体可控,同时不支持删除操作。布隆过滤器的应用场景丰富,在任何仅需要知道数据是否存在,并不关心具体数据内容的场景都可以使用布隆过滤器,例如在网页爬虫中URL去重防止重爬、可以应用在缓存系统中,避免缓存穿透等问题、在安全领域,也可以使用它来快速判断一个请求是否属于黑名单ip,防止恶意攻击等。
布隆过滤器拥有的快速插入和查找的特性是否很像散列表?普通散列表一般依赖数组实现,而布隆过滤器为了节省空间,使用的Bitmap结构,即位图。

位图

位图本质上其实也是散列表的一种实现,不同的是位图节省空间体现在它利用二进制位存储数据状态。我们知道在ASCII编码中,一个英文字符占用一个字节(Byte),也就是8位(bit)。若是UTF8编码,中文或者特殊字符可能会占用更多字节。例如存在一个数组如下:

  Integer[] array = new Integer[5]{0,1,3,5,7};

以上的数组中,不考虑数组对象本身占用的空间,只计算元素空间,每个元素若只占8位,存储这5个元素就要占用40位。假如用二进制位存储这5个元素,则只需要8位即可。


如上图,对应槽位的二进制位中,0代表不存在元素,1表示存在元素,当我们需要查询是否存在某个数字时,只需要看对应槽位的值是不是1即可,这样只需要一个8位空间即可表示0,1,3,5,7这几个元素了。

布隆过滤器的原理

上面说过,布隆过滤器是依赖位图实现的,它的原理是在位图的基础之上,定义了若干个散列函数。当需要向布隆过滤器中插入数据时,首先利用这几个散列函数分别计算出散列值,并且将位图上对应槽位的值设置成1,如果已经是1的话就不做任何操作。需要检查某个数据是否存在时也是同理,计算出要检查的数据的多个散列值,并且检查位图中对应槽位是否全都为1,但凡有一位不为1就可以断定值不存在,都为1的话值可能存在。


为什么是可能存在呢?随着计算的数据越来越多,查询的结果也会慢慢出现一定几率误差。因为散列函数存在结果碰撞的问题,两个不同的值通过散列函数计算出的结果有概率相同。所以当某个值即便从来没有被插入过,通过这多个散列函数计算的出的槽位也可能和之前值相同,所以会误判为已存在。为了降低误差几率,需要按照具体需求调整散列函数的算法\个数和位图大小,确保通过散列函数计算出来的槽位足够均匀分布到位图上。
布隆过滤器不支持删除操作,是因为在位图中每个槽位只存在两种状态,即0和1。一个槽位被设置为1,但无法确定它被多少个关键字,通过哪些散列函数设置了多少次1,只知道它被置为了1,所以无法删除。

布隆过滤器的应用

Google提供的guava工具里面包含了布隆过滤器的实现。

<dependency>
			<groupId>com.google.guava</groupId>
			<artifactId>guava</artifactId>
			<version>30.0-jre</version>
		</dependency>
  public static void main(String[] args) {
        Integer size = 1000000;
        BloomFilter<String> bloomFilter = BloomFilter.create(Funnels.stringFunnel(StandardCharsets.UTF_8), size, new Double(0.001));

        for (Integer i = 0; i < size; i++) {
            bloomFilter.put(String.valueOf(i));
        }

        Integer count = 0;
        for (Integer i = size; i < size * 2; i++) {
            if (bloomFilter.mightContain(String.valueOf(i))) {
                count++;
            }
        }

        System.out.println("误判率:" + count / new Double(size));
    }
误判率:0.00101

Process finished with exit code 0

测试结果和我们配置的参数大致吻合。

缓存穿透问题的产生,是当有大量的恶意流量去请求系统中不存在的数据时,缓存中没有对应数据,导致请求直接去查数据库,使数据库承受压力。而将布隆过滤器放在缓存之前则可以避免此问题,每当有流量来请求数据时会先在布隆过滤器中查询,请求是否是系统中的正常数据,如果是则放行流量去查缓存或数据库,否则直接忽略请求或者执行其他处理策略。

本文首发自Eric's Blog,转载请注明出处。

热门相关:韩国家庭妓女   两个母亲 未删减   商务旅行性风暴射液   兄弟姐妹的世界   家人们奇怪的近亲