• 布隆过滤器及其应用


    什么是布隆过滤器?

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

    位图

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

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

    以上的数组中,不考虑数组对象本身占用的空间,只计算元素空间,每个元素若只占8位,存储这5个元素就要占用40位。假如用二进制位存储这5个元素,则只需要8位即可。
    oDhLBr.png
    如上图,对应槽位的二进制位中,0代表不存在元素,1表示存在元素,当我们需要查询是否存在某个数字时,只需要看对应槽位的值是不是1即可,这样只需要一个8位空间即可表示0,1,3,5,7这几个元素了。

    布隆过滤器的原理

    上面说过,布隆过滤器是依赖位图实现的,它的原理是在位图的基础之上,定义了若干个散列函数。当需要向布隆过滤器中插入数据时,首先利用这几个散列函数分别计算出散列值,并且将位图上对应槽位的值设置成1,如果已经是1的话就不做任何操作。需要检查某个数据是否存在时也是同理,计算出要检查的数据的多个散列值,并且检查位图中对应槽位是否全都为1,但凡有一位不为1就可以断定值不存在,都为1的话值可能存在。
    oDyU0t.png
    为什么是可能存在呢?随着计算的数据越来越多,查询的结果也会慢慢出现一定几率误差。因为散列函数存在结果碰撞的问题,两个不同的值通过散列函数计算出的结果有概率相同。所以当某个值即便从来没有被插入过,通过这多个散列函数计算的出的槽位也可能和之前值相同,所以会误判为已存在。为了降低误差几率,需要按照具体需求调整散列函数的算法\个数和位图大小,确保通过散列函数计算出来的槽位足够均匀分布到位图上。
    布隆过滤器不支持删除操作,是因为在位图中每个槽位只存在两种状态,即0和1。一个槽位被设置为1,但无法确定它被多少个关键字,通过哪些散列函数设置了多少次1,只知道它被置为了1,所以无法删除。

    布隆过滤器的应用

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

    <dependency>
    			<groupId>com.google.guavagroupId>
    			<artifactId>guavaartifactId>
    			<version>30.0-jreversion>
    		dependency>
    
      public static void main(String[] args) {
            Integer size = 1000000;
            BloomFilter 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,转载请注明出处。

  • 相关阅读:
    基于Springboot外卖系统09:员工信息编辑+员工信息保存
    如何确定神经网络的参数 使得fx=tanhx
    34.LengthFieldBasedFrameDecoder代码使用
    React之Hooks基础
    容斥原理 能被整除的数
    C语言- 原子操作
    MySQL之BufferPool
    Linux之history命令详解
    一文弄懂CNN中的BatchNorm
    openresty安装配置,执行shell脚本
  • 原文地址:https://www.cnblogs.com/EricZhao-/p/17951719