布隆过滤器(Bloom Filter)全面讲解

目录

一. 前言

二. 使用场景

三. 布隆过滤器的原理

3.1. 数据结构

3.2. 空间计算

3.3. 增加元素

3.4. 查询元素

3.5. 修改元素

3.6. 删除元素

四. Redis 集成布隆过滤器

4.1. 版本要求

4.2. 安装 & 编译

4.3. Redis 集成

4.3.1. Redis 配置文件修改

4.3.2. 测试是否成功

五. Redis 中布隆过滤器指令使用

5.1. bf.add

5.2. bf.madd

5.3. bf.exists

5.4. bf.mexists

六. Java 本地内存使用布隆过滤器

6.1. 引入 Maven 依赖

6.2. 代码测试

6.3. 参数说明

6.4. fpp & expectedInsertions

七. Java 集成 Redis 使用布隆过滤器

7.1. 引入 Maven 依赖

7.2. 代码测试

八. 总结


一. 前言

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

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

二. 使用场景

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

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

三. 布隆过滤器的原理

3.1. 数据结构

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

大型位数组(二进制数组)

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

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

3.2. 空间计算

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

它们之间的关系比较简单:
1. 错误率越低,位数组越长,控件占用较大;
2. 错误率越低,无偏 hash 函数越多,计算耗时较长。

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

Bloom Filter Calculator

3.3. 增加元素

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

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

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

3.4. 查询元素

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

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

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

3.5. 修改元素

布隆过滤器没有修改元素的功能,也不需要。

3.6. 删除元素

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

四. Redis 集成布隆过滤器

4.1. 版本要求

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

redis-server -v

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

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. 安装 & 编译

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

1. 下载插件压缩包

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

2. 解压

tar -zxvf v2.2.6.tar.gz

3. 编译插件

cd RedisBloom-2.2.6/
make

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

4.3. Redis 集成

4.3.1. Redis 配置文件修改

1. 在 redis.conf 配置文件中加入如 RedisBloom 的 redisbloom.so 文件的地址;
2. 如果是集群则每个配置文件中都需要加入 redisbloom.so 文件的地址;
3. 添加完成后需要重启 redis。

loadmodule /usr/local/soft/RedisBloom-2.2.6/redisbloom.so

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

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

4.3.2. 测试是否成功

Redis 集成布隆过滤器的主要指令如下:
1. bf.add 添加一个元素。
2. bf.exists 判断一个元素是否存在。
3. bf.madd 添加多个元素。
4. bf.mexists 判断多个元素是否存在。

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

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

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

五. Redis 中布隆过滤器指令使用

5.1. bf.add

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

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

5.2. bf.madd

bf.madd 表示添加多个元素。

127.0.0.1:6379> bf.madd name luihuazhuimeng1 luihuazhuimeng2 luihuazhuimeng3
1) (integer) 1
2) (integer) 1
3) (integer) 1

5.3. bf.exists

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

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

5.4. bf.mexists

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

127.0.0.1:6379> bf.mexists name liuhuazhuimeng1 liuhuazhuimeng2 liuhuazhuimeng3
1) (integer) 1
2) (integer) 1
3) (integer) 0

六. Java 本地内存使用布隆过滤器

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

6.1. 引入 Maven 依赖

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

6.2. 代码测试

package com.lm.bf;

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

/**
* <p>
*        布隆过滤器测试代码
* </p>
*/
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);
    }
}

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

6.3. 参数说明

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

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

6.4. fpp & expectedInsertions

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

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

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

综上三次测试可以得出如下结论:
1. 当预计插入的值的数量不变时,偏差值 fpp 越小,位数组越大,hash 函数的个数越多。
2. 当偏差值不变时,预计插入的值的数量越大,位数组越大,hash 函数并没有变化(注意这个结论只是在 guava 实现的布隆过滤器中的算法符合,并不是说所有的算法都是这个结论,经过多次测试,确实 numHashFunctions 在 fpp 相同时,是不变的)。

七. Java 集成 Redis 使用布隆过滤器

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

7.1. 引入 Maven 依赖

<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;

/**
 * Java集成Redis使用布隆过滤器防止缓存穿透方案
 */
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);
    }
}

测试结果:

八. 总结

布隆过滤器的优点:

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

布隆过滤器的缺点:

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/212679.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【每日一题】可获得的最大点数

文章目录 Tag题目来源题目解读解题思路方法一&#xff1a;滑动窗口方法二&#xff1a;前缀和 写在最后 Tag 【滑动窗口】【前缀和】【数组】【2023-12-03】 题目来源 1423. 可获得的最大点数 题目解读 在一排卡牌中拿出 k 张卡牌&#xff0c;每次必须从这一排卡牌的开头或者…

基于OpenAPI工具包以及LSTM的CDN网络流量预测

基于LSTM的CDN网络流量预测 本案例是基于英特尔CDN以及英特尔 OpenAPI Intel Extension for TensorFlow* Intel oneAPIDPC Library 的网络流量预测&#xff0c;CDN是构建在现有网络基础之上的智能虚拟网络&#xff0c;目的是将源站内容分发至最接近用户的节点&#xff0c;使用…

视频生成的发展史及其原理解析:从Gen2、Emu Video到PixelDance、SVD、Pika 1.0

前言 考虑到文生视频开始爆发&#xff0c;比如11月份就是文生视频最火爆的一个月 11月3日&#xff0c;Runway的Gen-2发布里程碑式更新&#xff0c;支持4K超逼真的清晰度作品(runway是Stable Diffusion最早版本的开发商&#xff0c;Stability AI则开发的SD后续版本)11月16日&a…

Debian下载安装教程

目录 一.前言二.下载三.安装 一.前言 这篇文章展示如何使用VMware Workstation Player安装Debian12虚拟机。 二.下载 官网地址&#xff1a;官网 进入官网之后可以直接点击下载Debian选项&#xff0c;这样下载的是最新版的网络安装镜像。 三.安装 使用VMware Workstation P…

Concurrent Security of Anonymous Credentials Light, Revisited

目录 摘要引言 摘要 我们重新审视了著名的匿名证书轻&#xff08;ACL&#xff09;方案&#xff08;Baldimtsi和Lysyanskaya&#xff0c;CCS’13&#xff09;的并发安全保证。该方案最初被证明在按顺序执行时是安全的&#xff0c;其并发安全性仍然是一个悬而未决的问题。Benham…

GEE:Sobel算子卷积

作者&#xff1a;CSDN _养乐多_ 本文将深入探讨边缘检测中的一个经典算法&#xff0c;即Sobel算子卷积。我们将介绍该算法的基本原理&#xff0c;并演示如何在Google Earth Engine中应用Sobel算子进行图像卷积操作。并以试验区NDVI为例子&#xff0c;研究区真彩色影像、NDVI图…

Vue+SpringBoot解决session跨域问题

做了一个前后端分离&#xff0c;因为前后端的 session id不一致&#xff0c;导致前端请求时&#xff0c;后端的session读取不到对应的值&#xff0c;造成登录问题。 解决方法&#xff1a; SpringBoot项目: 添加一个跨域配置 代码如下: 或者controller使用CrossOrigin Conf…

单细胞个性化细胞注释

关于单细胞中级的课程内容&#xff0c;前面已经有了三次直播。欢迎回看&#xff5e; 单细胞直播一理解seurat数据结构与pbmc处理流程 单细胞直播二从GSE104154中理解seurat结构 单细胞直播三seurat数据结构与数据可视化 本期主要内容 本期指哪打哪&#xff0c;自己选定细胞&…

玻色量子事件活动

2023年 2023.7 玻色量子携最新相干光量子计算机惊艳亮相2023数字经济大会 2023.6 打造“新型计算数据中心”&#xff01;玻色量子与科华数据&#xff08;002335.SZ&#xff09;携手共创 2023.6 玻色量子“天工量子大脑”亮相中关村论坛&#xff0c;大放异彩 2023.5 100量…

美容院管理系统服务预约会员小程序效果如何

美容院在美业场景中需求度较高&#xff0c;尤其女性爱美悦己消费逐年增加&#xff0c;如清洁焕肤、祛皱抗衰、激光脱毛等美容项目都有不少需求者。 互联网深入美业行业多年&#xff0c;传统线下经营模式已经很难满足当今客户消费流程&#xff0c;如品牌寻找、服务预约、到店、…

用HeidiSQL在MySQL中创建新的数据库

用有权限的用户登录&#xff1a; 右键单击&#xff0c;选择&#xff1a; 输入要创建的数据库名称&#xff0c;然后点击“确定”&#xff1a; 刷新下&#xff0c;就看到新创建的数据库了&#xff1a; 在新创建的数据库中&#xff0c;就可以做其它操作了&#xff0c;例如…

Python标准库:random库【侯小啾python领航班系列(十七)】

Python标准库random【侯小啾python领航班系列(十七)】 大家好,我是博主侯小啾, 🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ�…

【SpringMVC】Spring Web MVC入门(一)

文章目录 前言什么是Spring Web MVC&#xff1f;什么是MVC什么是Spring MVC&#xff1f; Spring Boot 和 Spring MVC 的区别什么是Spring Boot&#xff1f;关系和区别 Spring MVC 学习注解介绍1. SpringBootApplication2. RestController3. RequestMapping3.1 RequestMapping 使…

(c语言)作业讲解

例一&#xff1a; 题目&#xff1a; 答案&#xff1a; #include<stdio.h> #include<math.h> int main() {int x;double sum0; int g 0; int i 0;scanf("%d",&x);while (x > 0){g x % 10;if (g % 2 0){g 0;}else{g 1;}sum g*pow(10,i);…

电子印章管理系统:是什么、3个平台推荐

说到印章&#xff0c;相信看过近现代电视剧的人都见过&#xff0c;一般在订立合约时最常用到&#xff0c;双方在合约上加盖印鉴&#xff0c;即代表着合约的成立。 我小时候还见过我父亲的印章&#xff0c;只是随着时代的发展&#xff0c;印章因为不易携带&#xff0c;容易被盗…

Spring事务传播机制

在上篇文章中&#xff0c;小编带领大家了解了Spring事务&#xff1a;Spring事务-CSDN博客&#xff0c;那么&#xff0c;本篇文章将会带领大家深入了解&#xff1a;Spring事务传播机制&#xff0c;感兴趣的各位老铁&#xff0c;欢迎深入探讨&#xff01;&#xff01; 事务传播机…

10行代码实现vue路由最简单的登陆拦截

需求&#xff1a;不涉及任何角色权限&#xff0c;基本实现目标&#xff0c;有token就可查看任何页面&#xff0c;否则就去登陆&#xff0c;来一步步实现 1. 创建你的路由页面&#xff0c;此处略了 2. 导航守卫拦截判断思路 // 创建路由 const router createRouter({history…

深度学习手势识别算法实现 - opencv python 计算机竞赛

文章目录 1 前言2 项目背景3 任务描述4 环境搭配5 项目实现5.1 准备数据5.2 构建网络5.3 开始训练5.4 模型评估 6 识别效果7 最后 1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 深度学习手势识别算法实现 - opencv python 该项目较为新颖…

智能优化算法应用:基于狮群算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于狮群算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于狮群算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.狮群算法4.实验参数设定5.算法结果6.参考文献7.MATLAB…

rman 0级 1级备份结合的注意事项 obsolete 和 FRA自动清理

1. 当心0级备份从controlfile中删除&#xff0c;0级备份决定recover windows时间 &#xff0c;一级不算 Incremental backup cycle: Sundays: Level 0 Monday-Sat: cumulative level 1 Every Friday and Saturday, the time for the incremental level 1 backup sud…