浅谈一下布隆过滤器的设计之美

news2024/10/6 8:32:13
  • 1 缓存穿透

  • 2 原理解析

  • 3 Guava实现

  • 4 Redisson实现

  • 5 实战要点

  • 6 总结

布隆过滤器是一个非常有用的数据结构。它可以在大规模数据中高效地判断某个元素是否存在。布隆过滤器的应用非常广泛,不仅在搜索引擎、防垃圾邮件等领域中经常用到,而且在许多知名项目中都有布隆过滤器的身影,比如RocketMQ、Hbase、Cassandra、LevelDB、RocksDB等。在后端程序开发中,学习和理解布隆过滤器的原理和实现非常重要。除了可以提高程序的效率和准确性,更可以开拓我们的思维,激发我们对程序设计的热情。

所以,让我们一起深入探讨布隆过滤器的设计之美吧

1 缓存穿透

我们先来看一个商品服务查询详情的接口:

 

 

假设此商品既不存储在缓存中,也不存在数据库中,则没有办法回写缓存 ,当有类似这样大量的请求访问服务时,数据库的压力就会极大。

这是一个典型的缓存穿透的场景。

为了解决这个问题呢,通常我们可以向分布式缓存中写入一个过期时间较短的空值占位,但这样会占用较多的存储空间,性价比不足。

问题的本质是:"如何以极小的代价检索一个元素是否在一个集合中 ?"

我们的主角布隆过滤器 出场了,它就能游刃有余的平衡好时间和空间两种维度 。

 

2 原理解析

布隆过滤器 (英语:Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量 和一系列随机映射函数 。

布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率 和查询时间 都远远超过一般的算法 ,缺点是有一定的误识别率和删除困难。

布隆过滤器的原理:当一个元素被加入集合时,通过 K 个散列函数将这个元素映射成一个位数组中的 K 个点,把它们置为 1。检索时,我们只要看看这些点是不是都是 1 就(大约)知道集合中有没有它了:如果这些点有任何一个 0 ,则被检元素一定不在 ;如果都是 1 ,则被检元素很可能在 。

简单来说就是准备一个长度为 m 的位数组并初始化所有元素为 0,用 k 个散列函数对元素进行 k 次散列运算跟 len (m) 取余得到 k 个位置并将 m 中对应位置设置为 1。

 

如上图,位数组的长度是8,散列函数个数是 3,先后保持两个元素x,y。这两个元素都经过三次哈希函数生成三个哈希值,并映射到位数组的不同的位置,并置为1。元素 x 映射到位数组的第0位,第4位,第7位,元素y映射到数组的位数组的第1位,第4位,第6位。

保存元素 x 后,位数组的第4位被设置为1之后,在处理元素 y 时第4位会被覆盖,同样也会设置为 1。

当布隆过滤器保存的元素越多 ,被置为 1 的 bit 位也会越来越多 ,元素 x 即便没有存储过,假设哈希函数映射到位数组的三个位都被其他值设置为 1 了,对于布隆过滤器的机制来讲,元素 x 这个值也是存在的,也就是说布隆过滤器存在一定的误判率 。

▍ 误判率

布隆过滤器包含如下四个属性:

  • k : 哈希函数个数

  • m : 位数组长度

  • n : 插入的元素个数

  • p : 误判率

若位数组长度太小则会导致所有 bit 位很快都会被置为 1 ,那么检索任意值都会返回”可能存在“ , 起不到过滤的效果。位数组长度越大,则误判率越小。

同时,哈希函数的个数也需要考量,哈希函数的个数越大,检索的速度会越慢,误判率也越小,反之,则误判率越高。

 

 

从张图我们可以观察到相同位数组长度的情况下,随着哈希函数的个人的增长,误判率显著的下降。

误判率 p 的公式是

  1. k 次哈希函数某一 bit 位未被置为 1 的概率为

  2. 插入 n 个元素后某一 bit 位依旧为 0 的概率为

  3. 那么插入 n 个元素后某一 bit 位置为1的概率为

  4. 整体误判率为

    当 m 足够大时,误判率会越小,该公式约等于

我们会预估布隆过滤器的误判率 p 以及待插入的元素个数 n 分别推导出最合适的位数组长度 m 和 哈希函数个数 k。

 

 布隆过滤器支持删除吗

布隆过滤器其实并不支持删除元素,因为多个元素可能哈希到一个布隆过滤器的同一个位置,如果直接删除该位置的元素,则会影响其他元素的判断。

▍ 时间和空间效率

布隆过滤器的空间复杂度为 O(m) ,插入和查询时间复杂度都是 O(k) 。存储空间和插入、查询时间都不会随元素增加而增大。空间、时间效率都很高。

▍哈希函数类型

Murmur3,FNV 系列和 Jenkins 等非密码学哈希函数适合,因为 Murmur3 算法简单,能够平衡好速度和随机分布,很多开源产品经常选用它作为哈希函数。

 

3 Guava实现

Google Guava是 Google 开发和维护的开源 Java开发库,它包含许多基本的工具类,例如字符串处理、集合、并发工具、I/O和数学函数等等。

1、添加Maven依赖

 

2、创建布隆过滤器

 

3、添加数据 

 

 

4、判断数据是否存在 

接下来,我们查看 Guava 源码中布隆过滤器是如何实现的 ? 

Guava 的计算位数组长度和哈希次数和原理解析这一节展示的公式保持一致。

重点来了,Bloom filter 是如何判断元素存在的 ?

方法名就非常有 google 特色 ,  ”mightContain “ 的中文表意是:”可能存在“ 。方法的返回值为 true ,元素可能存在,但若返回值为 false ,元素必定不存在。

 

4 Redisson实现

Redisson 是一个用 Java 编写的 Redis 客户端,它实现了分布式对象和服务,包括集合、映射、锁、队列等。Redisson的API简单易用,使得在分布式环境下使用Redis 更加容易和高效。

 1、添加Maven依赖

2、配置 Redisson 客户端 

3、初始化 

4、判断数据是否存在 

好,我们来从源码分析 Redisson 布隆过滤器是如何实现的 ? 

Bf配置信息

Redisson 布隆过滤器初始化的时候,会创建一个 Hash 数据结构的 key ,存储布隆过滤器的4个核心属性。

那么 Redisson 布隆过滤器如何保存元素呢 ?

 

从源码中,我们发现 Redisson 布隆过滤器操作的对象是 位图(bitMap) 。

在 Redis 中,位图本质上是 string 数据类型,Redis 中一个字符串类型的值最多能存储 512 MB 的内容,每个字符串由多个字节组成,每个字节又由 8 个 Bit 位组成。位图结构正是使用“位”来实现存储的,它通过将比特位设置为 0 或 1来达到数据存取的目的,它存储上限为 2^32 ,我们可以使用getbit/setbit命令来处理这个位数组。

为了方便大家理解,我做了一个简单的测试。

通过 Redisson API 创建 key 为 mybitset 的 位图  ,设置索引 3 ,5,6,8 位为 1 ,右侧的二进制值 也完全匹配。

5 实战要点

通过 Guava 和 Redisson 创建和使用布隆过滤器比较简单,我们下面讨论实战层面的注意事项。

1、缓存穿透场景

首先我们需要初始化 布隆过滤器,然后当用户请求时,判断过滤器中是否包含该元素,若不包含该元素,则直接返回不存在。

若包含则从缓存中查询数据,若缓存中也没有,则查询数据库并回写到缓存里,最后给前端返回。

 

2、元素删除场景

现实场景,元素不仅仅是只有增加,还存在删除元素的场景,比如说商品的删除。

原理解析这一节,我们已经知晓:布隆过滤器其实并不支持删除元素,因为多个元素可能哈希到一个布隆过滤器的同一个位置,如果直接删除该位置的元素,则会影响其他元素的判断 。

我们有两种方案:

▍计数布隆过滤器

计数过滤器(Counting Bloom Filter)是布隆过滤器的扩展,标准 Bloom Filter 位数组的每一位扩展为一个小的计数器(Counter),在插入元素时给对应的 k (k 为哈希函数个数)个 Counter 的值分别加 1,删除元素时给对应的 k 个 Counter 的值分别减 1。

 

虽然计数布隆过滤器可以解决布隆过滤器无法删除元素的问题,但是又引入了另一个问题:“更多的资源占用,而且在很多时候会造成极大的空间浪费 ”。

▍ 定时重新构建布隆过滤器

从工程角度来看,定时重新构建布隆过滤器 这个方案可行也可靠,同时也相对简单。

  1. 定时任务触发全量商品查询 ;

  2. 将商品编号添加到新的布隆过滤器 ;

  3. 任务完成,修改商品布隆过滤器的映射(从旧 A 修改成 新 B );

  4. 商品服务根据布隆过滤器的映射,选择新的布隆过滤器 B进行相关的查询操作 ;

  5. 选择合适的时间点,删除旧的布隆过滤器 A。


    6 总结 

 布隆过滤器 是一种常用于检索元素是否在集合中的数据结构。它由一个很长的二进制向量和一系列随机映射函数组成。虽然布隆过滤器存在一定的误判率(函数返回 true 表示元素可能存在,返回 false 表示元素必定不存在),但是它的空间效率和查询时间都远远超过一般的算法。而且,我们可以通过计数布隆过滤器和定时重新构建布隆过滤器两种方案来实现删除元素的效果。

布隆过滤器有四个核心属性:

k: 哈希函数个数

m: 位数组长度

n: 插入的元素个数

p: 误判率

在 Java 世界里,使用 Guava 和 Redisson 创建和使用布隆过滤器非常简单。因为布隆过滤器的设计精巧且简洁,工程上实现非常容易,效能高,所以在很多开源项目中被广泛使用。虽然它存在一定的误判率,但这是软件设计中 trade off 的一部分。因此,布隆过滤器是一种非常有用的数据结构,可以在很多应用场景中发挥作用。

 

 

 

 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

R语言单因素方差分析

R中的方差分析 介绍用于比较独立组的不同类型的方差分析,包括: 单因素方差分析:独立样本 t 检验的扩展,用于在存在两个以上组的情况下比较均值。这是方差分析检验的最简单情况,其中数据仅根据一个分组变量&#xff0…

【数据结构】七大排序总结

目录 🌾前言 🌾 内部排序 🌈1. 直接插入排序 🌈2. 希尔排序 🌈3. 直接选择排序 🌈4. 堆排序 🌈5. 归并排序 🌈6. 冒泡排序 🌈7. 快速排序 🌾外部排序 &…

4 月份 火火火火 的开源项目

盘点 4 月份 GitHub 上 Star 攀升最多的开源项目,整个 4 月份最火项目 90% 都是 AI 项目(准确的说,最近半年的热榜都是 AI 项目) 本期推荐开源项目目录: 1. AI 生成逼真语音 2. 复旦大模型 MOSS! 3. 让画中…

万万没想到在生产环境翻车了,之前以为很熟悉 CountDownLatch

前言 需求背景 具体实现 解决方案 总结 前言 之前我们分享了CountDownLatch的使用。这是一个用来控制并发流程的同步工具,主要作用是为了等待多个线程同时完成任务后,在进行主线程任务。然而,在生产环境中,我们万万没想到会…

【LeetCode】583. 两个字符串的删除操作

583. 两个字符串的删除操作(中等) 思路 这道题的状态定义和 1143. 最长公共子序列 相同,「定义一个 dp 数组,其中 dp[i]表示到位置 i 为止的子序列性质,并不是必须以 i 结尾」,此时 dp 数组的最后一位即为…

富士康终于醒悟了,重新加码中国制造,印度制造信不过

4月25日富士康在郑州揭牌新事业总部,显示出在扰攘了数年之后,富士康再度加强郑州富士康的发展力度,这应该是富士康在印度努力数年之后终于清醒了,印度制造终究不如中国制造可靠。 一、苹果和富士康在印度发展的教训 这两年苹果和富…

智能算法系列之基于粒子群优化的模拟退火算法

文章目录 前言1. 算法结合思路2. 问题场景2.1 Sphere2.2 Himmelblau2.3 Ackley2.4 函数可视化 3. 算法实现代码仓库:IALib[GitHub] 前言 本篇是智能算法(Python复现)专栏的第四篇文章,主要介绍粒子群优化算法与模拟退火算法的结合,以弥补各自…

【unity项目实战】3DRPG游戏开发07——其他详细的设计

敌人动画设计 新增图层动画,把权重设为1 在新图层默认新建一个空状态Base State,实现怪物默认动画播放Base State,因为Base State是空动画,所以默认会找上一个层的动画,这样就实现了两个图层动画的切换,也可以选择修改权重的方式实现 敌人随机巡逻 显示敌人巡逻的范…

网络字节序和主机字节序详解(附代码)

一、网络字节序和主机字节序 网络字节序和主机字节序是计算机网络中常用的两种数据存储格式。 主机字节序: 指的是在计算机内部存储数据时采用的字节排序方式。对于一个长为4个字节的整数,若采用大端字节序,则该整数在内存中的存储顺序是&a…

AppScan-被动手动扫描

被动扫描是针对性的扫描,浏览器代理到AppScan,然后进行手工操作,探索产生出的流量给AppScan进行扫描。这样可以使得扫描足够精准,覆盖率更加高,还能减少不必要的干扰 (一)环境准备 1、火狐安装…

SAP UI5 之Controls (控件) 笔记三

文章目录 官网 Walkthrough学习-Controls控件1.0.1 在index.html中使用class id 属性控制页面展示的属性1.0.2 我们在index.js文件中引入 text文本控制1.0.3打开浏览器查看结果 官网 Walkthrough学习-Controls控件 Controls控件 在前面展示在浏览器中的Hello World 是在Html …

Presto 之Hash Join的Partition

一. 前言 在Presto中,当两表Join为Hash Join并且join_distribution_type为PARTITIONED的时候,Presto会将Build表分区(Partition)后再进行Join操作。在Presto中的Join操作中,对表的分区有两级,第一级是将Has…

超简单搭建一个自用的ChatGPT网站(支持给网站添加访问密码)

前言: 有小伙伴留言想在自己的服务器搭建上图所示的ChatGPT网站,那么今天就是教大家如何在自己的服务器搭建像上图所示的ChatGPT网站 准备条件: 1)一台服务器(这里用centos7) 2)ChatGPT的API-KEY 一、Docker环境部署…

存储资源调优技术——SmartThin智能精简配置技术

目录 基本概念 工作原理 SmartThin关键技术 SmartThin主要功能 应用场景 精简LUN,存储空间超分配 按需动态分配存储资源,提高存储资源利用率 Thick和Thin LUN的区别如下 基本概念 Thin Lun属于存储资源的虚拟化,因此需要基于RAID 2.0存…

当影像遇上Python:用MoviePy库轻松搞定视频编辑

I. 简介 当影像遇上Python:用MoviePy库轻松搞定视频编辑 I. 简介II. 安装III. 使用 🚀🎬1. 创建一个视频剪辑对象2. 剪辑视频3. 剪切视频片段4. 改变视频尺寸和速度5. 合并视频6. 合并多个视频7. 用混合模式合并视频8. 添加音频9. 添加背景音…

台北房价预测

目录 1.数据理解1.1分析数据集的基本结构,查询并输出数据的前 10 行和 后 10 行1.2识别并输出所有变量 2.数据清洗2.1输出所有变量折线图2.2缺失值处理2.3异常值处理 3.数据分析3.1寻找相关性3.2划分数据集 4.数据整理4.1数据标准化 5.回归预测分析5.1线性回归&…

C++之深入解析如何实现一个线程池

一、基础概念 当进行并行的任务作业操作时,线程的建立与销毁的开销是,阻碍性能进步的关键,因此线程池,由此产生。使用多个线程,无限制循环等待队列,进行计算和操作,帮助快速降低和减少性能损耗…

Linux-初学者系列4_rpm-yum软件包管理

Linux-初学者系列4_rpm-yum软件包管理 一、软件包管理 系统软件安装后默认目录路径: /user/local /opt这两个目录用来存放用户自编译安装软件的目录,对于通过源码包安装的软件,如果没有指定安装目录,一般会装在以上目录中。 使…

利用Python轻松实现视频合成!

🎥 利用Python轻松实现视频合成!💻 你是否曾经尝试过在一个视频中添加另一个小视频的场景呢?如果是,你一定会知道这是一项令人头疼的任务。但是,有了Python的 moviepy 库,这个任务将变得非常简单…

Java AIO(Java Asynchronous I/O:异步非阻塞IO)

1.基本介绍 1>.JDK7引入了Asynchronous I/O,即AIO.在进行I/O编程中,常用到两种模式:Reactor和Proactor; 2>.Java的NIO就是Reactor,当有事件触发时,服务器端得到通知,进行相应的处理; 3>.AIO即NIO2.0,叫做异步不阻塞的IO.AIO引入了异步通道的概念,采用了Proactor模式…