布隆(Bloom Filter)过滤器——全面讲解,建议收藏-程序员宅基地

技术标签: 面试  java  《Redis从入门到精通》系列  数据库  redis  

本文已收录于专栏

️《Redis之大厂必备技能包》️

欢迎各位关注、三连博主的文章及专栏,全套Redis学习资料,大厂必备技能!


 目录

1、什么是布隆过滤器

2、布隆过滤器的使用场景

3、布隆过滤器的原理

3.1 数据结构

3.2 空间计算

3.3 增加元素

3.4 查询元素

3.5 修改元素

3.6 删除元素

4、Redis集成布隆过滤器

4.1 版本要求

4.2 安装&编译

4.3 Redis集成

5、Redis中布隆过滤器指令使用

5.1 bf.add

5.2 bf.madd

5.3 bf.exists

5.3 bf.mexists

6、Java本地内存使用布隆过滤器

6.1 引入pom依赖

6.2 编写测试代码

6.3 测试结果

6.4 参数说明

6.5 fpp&expectedInsertions

7、Java集成Redis使用布隆过滤器

7.1 引入pom依赖

7.2 编写测试代码

7.3 测试结果


1、什么是布隆过滤器

布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。

上面这句介绍比较全面的描述了什么是布隆过滤器,如果还是不太好理解的话,就可以把布隆过滤器理解为一个set集合,我们可以通过add往里面添加元素,通过contains来判断是否包含某个元素。由于本文讲述布隆过滤器时会结合Redis来讲解,因此类比为Redis中的Set数据结构会比较好理解,而且Redis中的布隆过滤器使用的指令与Set集合非常类似(后续会讲到)。

学习布隆过滤器之前有必要先聊下它的优缺点,因为好的东西我们才想要嘛!
布隆过滤器的优点:

  • 时间复杂度低,增加和查询元素的时间复杂为O(N),(N为哈希函数的个数,通常情况比较小)
  • 保密性强,布隆过滤器不存储元素本身
  • 存储空间小,如果允许存在一定的误判,布隆过滤器是非常节省空间的(相比其他数据结构如Set集合)

布隆过滤器的缺点:

  • 有点一定的误判率,但是可以通过调整参数来降低
  • 无法获取元素本身
  • 很难删除元素

2、布隆过滤器的使用场景

布隆过滤器可以告诉我们 “某样东西一定不存在或者可能存在”,也就是说布隆过滤器说这个数不存在则一定不存,布隆过滤器说这个数存在可能不存在(误判,后续会讲),**利用这个判断是否存在的特点可以做很多有趣的事情。

  • 解决Redis缓存穿透问题(面试重点)
  • 邮件过滤,使用布隆过滤器来做邮件黑名单过滤
  • 对爬虫网址进行过滤,爬过的不再爬
  • 解决新闻推荐过的不再推荐(类似抖音刷过的往下滑动不再刷到)
  • HBase\RocksDB\LevelDB等数据库内置布隆过滤器,用于判断数据是否存在,可以减少数据库的IO请求

3、布隆过滤器的原理

3.1 数据结构

布隆过滤器它实际上是一个很长的二进制向量和一系列随机映射函数。以Redis中的布隆过滤器实现为例,Redis中的布隆过滤器底层是一个大型位数组(二进制数组)+多个无偏hash函数。
一个大型位数组(二进制数组)

位数组.png


多个无偏hash函数:
无偏hash函数就是能把元素的hash值计算的比较均匀的hash函数,能使得计算后的元素下标比较均匀的映射到位数组中。

如下就是一个简单的布隆过滤器示意图,其中k1、k2代表增加的元素,a、b、c即为无偏hash函数,最下层则为二进制数组。

布隆过滤器.png

3.2 空间计算

在布隆过滤器增加元素之前,首先需要初始化布隆过滤器的空间,也就是上面说的二进制数组,除此之外还需要计算无偏hash函数的个数。布隆过滤器提供了两个参数,分别是预计加入元素的大小n,运行的错误率f。布隆过滤器中有算法根据这两个参数会计算出二进制数组的大小l,以及无偏hash函数的个数k。
它们之间的关系比较简单:

  • 错误率越低,位数组越长,控件占用较大
  • 错误率越低,无偏hash函数越多,计算耗时较长

如下地址是一个免费的在线布隆过滤器在线计算的网址:

https://krisives.github.io/bloom-calculator/

image.png

3.3 增加元素

往布隆过滤器增加元素,添加的key需要根据k个无偏hash函数计算得到多个hash值,然后对数组长度进行取模得到数组下标的位置,然后将对应数组下标的位置的值置为1

  • 通过k个无偏hash函数计算得到k个hash值
  • 依次取模数组长度,得到数组索引
  • 将计算得到的数组索引下标位置数据修改为1

例如,key = Liziba,无偏hash函数的个数k=3,分别为hash1、hash2、hash3。三个hash函数计算后得到三个数组下标值,并将其值修改为1.
如图所示:

增加元素.png

3.4 查询元素

布隆过滤器最大的用处就在于判断某样东西一定不存在或者可能存在,而这个就是查询元素的结果。其查询元素的过程如下:

  • 通过k个无偏hash函数计算得到k个hash值
  • 依次取模数组长度,得到数组索引
  • 判断索引处的值是否全部为1,如果全部为1则存在(这种存在可能是误判),如果存在一个0则必定不存在

关于误判,其实非常好理解,hash函数在怎么好,也无法完全避免hash冲突,也就是说可能会存在多个元素计算的hash值是相同的,那么它们取模数组长度后的到的数组索引也是相同的,这就是误判的原因。例如李子捌和李子柒的hash值取模后得到的数组索引都是1,但其实这里只有李子捌,如果此时判断李子柒在不在这里,误判就出现啦!因此布隆过滤器最大的缺点误判只要知道其判断元素是否存在的原理就很容易明白了!

3.5 修改元素


3.6 删除元素

布隆过滤器对元素的删除不太支持,目前有一些变形的特定布隆过滤器支持元素的删除!关于为什么对删除不太支持,其实也非常好理解,hash冲突必然存在,删除肯定是很苦难的!

4、Redis集成布隆过滤器

4.1 版本要求

  • 推荐版本6.x,最低4.x版本,可以通过如下命令查看版本:
redis-server -v

image.png

  • 插件安装,网上大部分推荐v1.1.1,文章写的时候v2.2.6已经是release版本了,用户自己选择,地址全在下面(2.2.6官网介绍说是1.0版本的维护版本,如果不想使用新的功能,无需升级!)

image.png


v1.1.1

https://github.com/RedisLabsModules/rebloom/archive/v1.1.1.tar.gz

v2.2.6

https://github.com/RedisLabsModules/rebloom/archive/v2.2.6.tar.gz

4.2 安装&编译

以下安装全部在指定目录下完成,可以选择一个合适的统一目录进行软件安装和管理。

4.2.1 下载插件压缩包

wget https://github.com/RedisLabsModules/rebloom/archive/v2.2.6.tar.gz

4.2.2 解压

tar -zxvf v2.2.6.tar.gz

4.2.3 编译插件

cd RedisBloom-2.2.6/
make

image.png

编译成功后看到redisbloom.so文件即可

4.3 Redis集成

4.3.1 Redis配置文件修改

  • redis.conf配置文件中加入如RedisBloom的redisbloom.so文件的地址
  • 如果是集群则每个配置文件中都需要加入redisbloom.so文件的地址
  • 添加完成后需要重启redis
loadmodule /usr/local/soft/RedisBloom-2.2.6/redisbloom.so

redis.conf配置文件中预置了loadmodule的配置项,我们可以直接在这里修改,后续修改会更加方便。

image.png

保存退出后一定要记得重启Redis!
保存退出后一定要记得重启Redis!
保存退出后一定要记得重启Redis!

4.3.2 测试是否成功

Redis集成布隆过滤器的主要指令如下:

  • bf.add 添加一个元素
  • bf.exists 判断一个元素是否存在
  • bf.madd 添加多个元素
  • bf.mexists 判断多个元素是否存在

连接客户端进行测试,如果指令有效则证明集成成功

image.png

如果出现如下情况(error) ERR unknown command ,可以通过如下方法检查:

  • SHUTDOWN Redis实例,再重启实例,再次测试
  • 检查配置文件是否配置redisbloom.so文件地址正确
  • 检查Redis的版本是否过低

image.png

5、Redis中布隆过滤器指令使用

5.1 bf.add

bf.add表示添加单个元素,添加成功返回1

127.0.0.1:6379> bf.add name liziba
(integer) 1

image.png

5.2 bf.madd

bf.madd表示添加多个元素

127.0.0.1:6379> bf.madd name liziqi lizijiu lizishi
1) (integer) 1
2) (integer) 1
3) (integer) 1

image.png

5.3 bf.exists

bf.exists表示判断元素是否存在,存在则返回1,不存在返回0

127.0.0.1:6379> bf.mexists name liziba
1) (integer) 1

image.png

5.3 bf.mexists

bf.mexists表示判断多个元素是否存在,存在的返回1,不存在的返回0

127.0.0.1:6379> bf.mexists name liziqi lizijiu liziliu
1) (integer) 1
2) (integer) 1
3) (integer) 0

image.png

6、Java本地内存使用布隆过滤器

使用布隆过滤器的方式有很多,还有很多大佬自己手写的,我这里使用的是谷歌guava包中实现的布隆过滤器,这种方式的布隆过滤器是在本地内存中实现。

6.1 引入pom依赖

<dependency>
  <groupId>com.google.guava</groupId>
  <artifactId>guava</artifactId>
  <version>29.0-jre</version>
</dependency>

6.2 编写测试代码

package com.lizba.bf;

import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;

/**
 * <p>
 *        布隆过滤器测试代码
 * </p>
 *
 * @Author: Liziba
 * @Date: 2021/8/29 14:51
 */
public class BloomFilterTest {

    /** 预计插入的数据 */
    private static Integer expectedInsertions = 10000000;
    /** 误判率 */
    private static Double fpp = 0.01;
    /** 布隆过滤器 */
    private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), expectedInsertions, fpp);

    public static void main(String[] args) {
        // 插入 1千万数据
        for (int i = 0; i < expectedInsertions; i++) {
            bloomFilter.put(i);
        }

        // 用1千万数据测试误判率
        int count = 0;
        for (int i = expectedInsertions; i < expectedInsertions *2; i++) {
            if (bloomFilter.mightContain(i)) {
                count++;
            }
        }
        System.out.println("一共误判了:" + count);

    }

}

6.3 测试结果

误判了100075次,大概是expectedInsertions(1千万)的0.01,这与我们设置的 fpp = 0.01非常接近。

image.png

6.4 参数说明

在guava包中的BloomFilter源码中,构造一个BloomFilter对象有四个参数:

  • Funnel funnel:数据类型,由Funnels类指定即可
  • long expectedInsertions:预期插入的值的数量
  • fpp:错误率
  • BloomFilter.Strategy:hash算法

6.5 fpp&expectedInsertions

  • 当expectedInsertions=10000000&&fpp=0.01时,位数组的大小numBits=95850583,hash函数的个数numHashFunctions=7

image.png

  • 当expectedInsertions=10000000&&fpp=0.03时,位数组的大小numBits=72984408,hash函数的个数numHashFunctions=5

image.png

  • 当expectedInsertions=100000&&fpp=0.03时,位数组的大小numBits=729844,hash函数的个数numHashFunctions=5

image.png

综上三次测试可以得出如下结论:

  • 当预计插入的值的数量不变时,偏差值fpp越小,位数组越大,hash函数的个数越多
  • 当偏差值不变时,预计插入的中的数量越大,位数组越大,hash函数并没有变化(注意这个结论只是在guava实现的布隆过滤器中的算法符合,并不是说所有的算法都是这个结论,我做了多次测试,确实numHashFunctions在fpp相同时,是不变的!)

7、Java集成Redis使用布隆过滤器

Redis经常会被问道缓存击穿问题,比较优秀的解决办法是使用布隆过滤器,也有使用空对象解决的,但是最好的办法肯定是布隆过滤器,我们可以通过布隆过滤器来判断元素是否存在,避免缓存和数据库都不存在的数据进行查询访问!在如下的代码中只要通过bloomFilter.contains(xxx)即可,我这里演示的还是误判率!

7.1 引入pom依赖

<dependency>
  <groupId>org.redisson</groupId>
  <artifactId>redisson-spring-boot-starter</artifactId>
  <version>3.16.0</version>
</dependency>

7.2 编写测试代码

package com.lizba.bf;

import org.redisson.Redisson;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;

/**
 * <p>
 *      Java集成Redis使用布隆过滤器防止缓存穿透方案
 * </p>
 *
 * @Author: Liziba
 * @Date: 2021/8/29 16:13
 */
public class RedisBloomFilterTest {

    /** 预计插入的数据 */
    private static Integer expectedInsertions = 10000;
    /** 误判率 */
    private static Double fpp = 0.01;

    public static void main(String[] args) {
        // Redis连接配置,无密码
        Config config = new Config();
        config.useSingleServer().setAddress("redis://192.168.211.108:6379");
        // config.useSingleServer().setPassword("123456");

        // 初始化布隆过滤器
        RedissonClient client = Redisson.create(config);
        RBloomFilter<Object> bloomFilter = client.getBloomFilter("user");
        bloomFilter.tryInit(expectedInsertions, fpp);

        // 布隆过滤器增加元素
        for (Integer i = 0; i < expectedInsertions; i++) {
            bloomFilter.add(i);
        }

        // 统计元素
        int count = 0;
        for (int i = expectedInsertions; i < expectedInsertions*2; i++) {
            if (bloomFilter.contains(i)) {
                count++;
            }
        }
        System.out.println("误判次数" + count);

    }

}

7.3 测试结果

image.png

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_41125219/article/details/119982158

智能推荐

已解决(selenium操作火狐浏览器报错)TypeError: __init__() got an unexpected keyword argument ‘firefox_options‘-程序员宅基地

文章浏览阅读1.6w次,点赞8次,收藏18次。已解决(selenium操作火狐浏览器报错)TypeError: __init__() got an unexpected keyword argument ‘firefox_options‘_typeerror: __init__() got an unexpected keyword argument 'chrome_options

python方差_Python编程:方差、标准差、均方差、均方根值、均方误差、均方根误差...-程序员宅基地

文章浏览阅读315次。python实现代码# -*- coding: utf-8 -*-import mathdef get_average(records):"""平均值"""return sum(records) / len(records)def get_variance(records):"""方差 反映一个数据集的离散程度"""average = get_average(records)return sum(..._pycharm方差计算代码

jmeter接口测试实例(五)对cookies的自动管理,实现登录后修改用户信息_jmeter中post请求如何更改每次的用户名-程序员宅基地

文章浏览阅读902次。一、前提:cookie是保存在本地,用来记录用户信息,最典型的作用是判断用户是否已经登录。如果一个接口,需要用户登录后,才能进行操作,如下,修改用户信息那么这时候,就需要用到cookie去识别这个登录的用户,因为要修改的是这个登录的用户的信息。二、使用1、jmeter.properties 中 将CookieManager.save.cookies 设置为true。完成后重启jmet..._jmeter中post请求如何更改每次的用户名

python新建项目没有venv_pycharm配置venv虚拟环境-程序员宅基地

文章浏览阅读7.5k次,点赞2次,收藏2次。虚拟环境的好处:搭建独立的python运行环境,不与其他产生冲突虚拟环境有助于包的管理和防止版本冲突3.删除卸载方便虚拟环境的搭建:1.进入python的Scripts下,执行:pip3 install virtualenv2.选择建立虚拟环境的文件夹,我这边是直接在D盘software下面创建了一个virtualenv,如图:image.png3.创建虚拟环境:virtualenv --no-s..._pycharm没有venv怎么办

M1芯片的Mac在开发iOS项目时遇到的问题汇总(模拟器无法运行,Cocoapods错误等)_oc cocospods 安装依赖库之后不能使用模拟器了-程序员宅基地

文章浏览阅读2.3k次。--pod install时报错,且错误提示中有“ffi”字眼,提示错误:/Library/Ruby/Gems/2.6.0/gems/ffi-1.15.3/lib/ffi/library.rb:275: [BUG] Bus Error at 0x00000001042fc000 ruby 2.6.3p62 (2019-04-16 revision 67580) [universal.arm64e-darwin20]解决方案:juejin.cn/post/698064…--pod repo upd_oc cocospods 安装依赖库之后不能使用模拟器了

编辑器VSCode使用心得-程序员宅基地

文章浏览阅读540次。工欲善其事必先利其器,趁手的工具会使我们开发事半功倍。市面上的编辑器我用过许多,编辑器使用经历Notepad++,(开源)这个应该是最轻量级的吧,查看代码还好,编辑代码就算了官网地址:https://notepad-plus-plus.org/Brackets,(开源)这个也不错,github-star:30k了,上次发布版本是6月..._vscode nextick

随便推点

西部数码mysql怎么用织梦_[原创]织梦程序(DeDeCms)常见问题集锦-程序员宅基地

文章浏览阅读95次。前言:织梦程序是最知名的cms程序,使用广泛,但很多朋友对织梦还不太熟悉,通过工单分析得知,经常容易出现本文中的问题,本次统一整理出来,希望对新手朋友有帮助,本文写的非常详细,请仔细阅读,一、描述:“dedecms错误警告:连接数据库失败,可能数据库密码不对或数据库服务器出错”如图:分析:用织梦程序制作的站点做迁移服务器后容易出现这个问题,原因是程序中设置的数据库连接信息有误连接不到数据库,解决方..._西部数码支不支持织梦的程序

免安装mysql启动3534错误处理_mysql 免安装版本启动时 3534出错_mysql-程序员宅基地

文章浏览阅读295次。上午在win7下安装MYSQL,只到“net start mysql”这一步报错:3534的错误:于是在百度中搜索关键字“mysql服务无法启动3534”。参考以下两个链接中的方法,解决了3534启动失败的问题:mysql服务无法启动3534错误。http://jingyan.baidu.com/article/219f4bf7e978fcde442d38a9.htmlhttp://blog.cs..._免安装mysql启动失败

评测了10款画流程图软件,这4款最好用!(完全免费)_好用的流程图软件免费-程序员宅基地

文章浏览阅读5.1w次,点赞58次,收藏295次。最近在做项目和复习的时候,用了不少流程图软件给我帮了大忙,所以今天就来分享分享你在网上搜索一下流程图软件,能找到很多很多:但这些软件多数并不是专门绘制流程图的软件,它可能是一些思维导图软件、或者说一些产品交互原型图软件,使用时或多或少有些麻烦。而且,普遍这些软件缺点也很多,比如:只有在线版:ProcessOn(https://www.processon.com)导出功能收费:迅捷流程图软件体积庞大:VISIO就没有一款简单易用、绿色免费的流程图软件吗?阿虚花了不少时间,挨着_好用的流程图软件免费

在CentOS6.9安装Azkaban3.50.0_centos6.9 azkaban-程序员宅基地

文章浏览阅读1.4k次。cd /data/install_pkgwget https://github.com/azkaban/azkaban/archive/3.50.0.tar.gztar -zxvf 3.50.0.tar.gzvi /data/install_pkg/azkaban-3.50.0/azkaban-common/src/main/java/azkaban/utils/EmailMessage..._centos6.9 azkaban

Java项目基于ssm+vue.js的学生宿舍维修服务平台附带文章和源代码设计说明文档ppt-程序员宅基地

文章浏览阅读586次,点赞23次,收藏30次。博主介绍:CSDN特邀作者、985计算机专业毕业、某互联网大厂高级全栈开发程序员、码云/掘金/华为云/阿里云/InfoQ/StackOverflow/github等平台优质作者、专注于Java、小程序、前端、python等技术领域和毕业项目实战,以及程序定制化开发、全栈讲解、就业辅导、面试辅导、简历修改。精彩专栏 推荐订阅2023-2024年最值得选的微信小程序毕业设计选题大全:100个热门选题推荐2023-2024年最值得选的Java毕业设计选题大全:500个热门选题推荐。

ultralytics的YOLOv8改为自用版本_from ultralytics.utils.plotting import annotator, -程序员宅基地

文章浏览阅读944次。由于需要用pyqt给yolov8做一个界面,而ultralytics一层嵌一层,不是很好用,所以对它的这个源码进行精简,具体代码我放到了这里,ultralytics使用的版本是8.0.54。具体代码如下,需要根据自己的情况来修改data的配置文件以及权值文件,在代码的49和50行。_from ultralytics.utils.plotting import annotator, colors, save_one_box modul

推荐文章

热门文章

相关标签