布隆过滤器

145 阅读4分钟

什么是布隆过滤器?

背景:

官网解释:Low latency and compact probabilistic data structures(低延迟和紧凑的概率数据结构)

布隆过滤器(Bloom Filter)是一种用于判重的数据结构,其主要目的是在大规模数据集中高效地检查一个元素是否存在。布隆过滤器最早由Burton Howard Bloom于1970年提出,用于解决大规模数据判重问题。

应用场景:

  • 避免缓存穿透
  • 检查用户名是否重复
  • 例如网络爬虫判重URL
  • 解决推送过的文章、视频不再推送(抖音往下刷不再刷到相同视频)
  • 邮件服务器判重邮件地址等。

传统的哈希表等数据结构在处理大规模数据时会占用大量的内存,而布隆过滤器通过使用位数组和多个哈希函数来解决这个问题。

优缺点:

优点:

  • 占用内存少:相比于传统的哈希表,布隆过滤器使用位数组和哈希函数,占用的内存要少得多。
  • 查询速度快:由于只需要计算多个哈希函数并查询位数组,布隆过滤器的查询速度非常快,判断时间复杂读O(N),(N是hash函数的个数)。
  • 保密性好:布隆过滤器不存储数据本身

缺点:

  • 误判率:由于多个元素可能映射到同一个位数组位置,因此存在一定的误判率,即判断一个元素不存在时,实际上可能存在。但可以通过调整位数组大小和使用更多的哈希函数来降低误判率。
  • 很难删除元素(最好对长期存储、不轻易删除的数据使用布隆过滤器)【Cuckoo Filter可删除元素】
  • 不能获取数据本身,只能用于判重
  • 随着数据量的增加,误判率越来越高(可以理解为位数组为0的位置越来越少,发生hash冲突的概率越来越大)

工作原理:

布隆过滤器由一个位数组(Bit Array)和多个哈希函数组成。初始时,位数组的所有位都被置为0。当要添加一个元素时,会将这个元素经过多个哈希函数得到多个哈希值,然后将对应的位数组位置置为1。判断一个元素是否存在时,同样通过多个哈希函数计算出位数组位置,如果所有位置都是1,则说明可能存在,如果有任何一个位置为0,则说明该元素一定不存在。

image.png

  • 对三个元素分别进行用3个hash函数计算其hash值,通过一个数组存储,比如:"123"用hash1求值为0,则将arr[0]设置为1
  • 在判断的时候只需用hash函数对带判断元素求值value,若存在一个arr[value]==0则此元素不存在,如果都为1则存在
  • 因为hash函数会产生hash冲突(两个不相同的元素通过hash函数的计算得出的值是一样的),所以布隆过滤器所判断的存在不一定存在,也可能是发生了hash冲突

布隆过滤器说不存在,就一定不存在;说存在不一定存在

怎么用JAVA操作布隆过滤器?

redis能够集成布隆过滤器,但是过程比较麻烦

  • 笔者使用的是Linux虚拟机(CentOS7),安装了redis 6.0.5,然后用主机:window去连接

  • 在虚拟机中,去官网下载了RedisBloom-2.2.18,解压并编译

  • 配置redis.config文件,

    • 设置protected-modeno(是否开启保护模式。yes只允许本地访问,no可以远程访问)
    • 设置daemonizeyes(是否后台运行,yes为是,no为否)
    • bind 127.0.0.0改成bind 0.0.0.0(只允许本机的连接请求。修改后可以接受任意请求)
    • 添加RedisBloom编译后的os文件位置(我的是:loadmodule /usr/local/redis/RedisBloom-2.2.18/redisbloom.so)
    • 然后是修改防火墙,打开redis的服务器端口(我的是:6379)
  • 使用redisson操作布隆过滤器

  • 引入pom.xml依赖

        <dependencies>
            <dependency>
                <groupId>org.redisson</groupId>
                <artifactId>redisson</artifactId>
                <version>3.13.6</version>
            </dependency>
        </dependencies>
    
  • java代码:

    1. 先创建config

      Config config = new Config();
      config.useClusterServers()
            .addNodeAddress("redis://ip地址:端口号");
      
    2. 创建客户端

      RedissonClient redisson = Redisson.create(config);
      
    3. 创建一个存储Long类型的布隆过滤器,并初始化

      RBloomFilter<Long> numFilter = redisson.getBloomFilter("numFilter");
      numFilter.tryInit(位数组长度, 误判率);//误判率值为0~1之间的小数
      
    4. 增加元素并打印布隆过滤器元素个数(往往不等于n值)

      for (long i = 0; i < n; i++) {
          System.out.println("添加了"+i);
          numFilter.add(i);
      }
      long elementCount = numFilter.count();
      System.out.println("elementCount ="+elementCount);
      
    5. 计算误判次数(用n个存在数字进行判断),输出值大都能匹配上之前设置的误判率

      int count = 0;
      for (long i = n; i < n * 2; i++) {
          if (numFilter.contains(i)) {
              System.out.println("误判:"+i);
              count++;
          }
      }
      System.out.println("误判次数 ="+count);
      
    1. 删除这个布隆过滤器,并关闭连接

      numFilter.delete();
      redisson.shutdown();