> 文章列表 > 聊聊布隆过滤器

聊聊布隆过滤器

聊聊布隆过滤器

目录

一、什么是布隆过滤器

二、使用场景

三、原理

四、使用

4.1、Guava实现

4.2、Redisson实现


一、什么是布隆过滤器?

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

        布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是高效的插入和查询,而且非常节省空间,缺点是hash 碰撞造成的误识别率和删除困难。

二、使用场景

        一般使用较多的场景就是避免缓存穿透,具体场景就是在使用 Reids 做数据缓存的时候,很有可能会遇到一个问题:用户想要查询一个数据,发现 redis 缓存没有命中,于是向数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是都去请求数据库。这会给数据库造成很大的压力,这时候就相当于出现了缓存穿透。缓存穿透也是实际开发中必须要避免和提前预防的内容之一。

避免缓存穿透,有以下几种解决方案:
1、缓存空值。为了解决这个问题呢,通常我们可以向分布式缓存中写入一个过期时间较短的空值占位,但这样会占用较多的存储空间,性价比不足。

2、使用 HashMap。将需要查询的 key 提前加载到 HashMap 中,因为 HashMap 查找的时间复杂度为 O(1) ,因此通过映射可以快速查找到相应的 Key 来判定 Key 是否存在 ,如果查不出来就没必要继续查找缓存了。但是这样做的话极其消耗宝贵的内存,数据量大的情况下成本也会上升。此种做法会对内容造成不可测的负担,不推荐使用。

3、使用 Bloom Filter。原理上和使用 HashMap 一样,但更省空间。此种做法为主流做法,推荐使用布隆过滤器。

三、原理

布隆过滤器的原理:当一个元素被加入集合时,通过 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 这个值也是存在的,也就是说布隆过滤器存在一定的误判率

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

同时,哈希函数的个数也需要考量,哈希函数的个数越大,检索的速度会越慢,误判率也越小,反之,则误判率越高。相同位数组长度的情况下,随着哈希函数的个人的增长,误判率显著的下降。

布隆过滤器支持删除吗

布隆过滤器其实并不支持删除元素,因为多个元素可能哈希到一个布隆过滤器的同一个位置,如果直接删除该位置的元素,则会影响其他元素的判断。但我们可以通过计数布隆过滤器(不推荐)定时重新构建布隆过滤器两种方案实现删除元素的效果。

四、使用

4.1、Guava实现

添加Maven依赖

<dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>22.0</version>
</dependency>
package com.cjian.boomfilter;import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;import java.nio.charset.StandardCharsets;/*** @Author: cjian* @Date: 2023/4/20 11:53* @Des:*/
public class Demo {static BloomFilter<String> filter;static {filter = BloomFilter.create(//Funnel 是一个接口,用于将任意类型的对象转换为字节流,//以便用于布隆过滤器的哈希计算。Funnels.stringFunnel(StandardCharsets.ISO_8859_1),10000,  // 插入数据条目数量0.001  // 误判率);addDate();}public static void main(String[] args) {System.out.println(mayContain("aaa"));System.out.println(mayContain("bbb"));}private static void addDate() {System.out.println("初始化布隆过滤器数据开始");//插入2个元素filter.put("aaa");filter.put("bbb");System.out.println("初始化布隆过滤器数据结束");}public static boolean mayContain(String id) {return filter.mightContain(id);}
}

4.2、Redisson实现

添加Maven依赖 

<dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.16.1</version>
</dependency>
package com.cjian.boomfilter;import org.redisson.Redisson;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;/*** @Author: cjian* @Date: 2023/4/20 13:54* @Des:*/
public class RedissonDemo {static RBloomFilter<String> bloomFilter;static {Config config = new Config();config.useSingleServer().setAddress("redis://localhost:6379");bloomFilter = Redisson.create(config).getBloomFilter("myBloomFilter");}public static void main(String[] args) {init();System.out.println(mightContain("aaaa"));System.out.println(mightContain("bbbb"));}private static void init() {//10000表示插入元素的个数,0.001表示误判率bloomFilter.tryInit(10000, 0.001);//插入2个元素bloomFilter.add("aaaa");bloomFilter.add("bbbb");}private static boolean mightContain(String id) {return bloomFilter.contains(id);}
}