【Java项目】布隆过滤器解决缓存穿透问题以及布隆过滤器删除困难问题

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 【Java项目】布隆过滤器解决缓存穿透问题以及布隆过滤器删除困难问题

什么是缓存穿透问题

访问浏览器,用户访问那些存在在Redis或者数据库中的数据的时候都能正常返回结果,但是如果查询那些不存在于Redis中也不存在于数据库中的数据,那么这些无意义的查询如果非常多,都将直接穿过Redis而导向数据库,导致数据库压力提高,造成宕机。

因此,缓存穿透就是指用户访问那些在数据库和Redis中都不存在的数据,例如我们知道id采用自增策略,那么就不可能出现负数id,而如果不法分子使用负数id进行查询,那么这些请求都会穿过Redis直接向数据库发送请求,从而导致数据库压力骤增,导致数据库宕机。(一般是恶意行为)

那么如何解决缓存穿透问题?

  • 缓存空对象,每次发送这种查询不到的id的时候都把这些id缓存到Redis中,并且设定值为空(null),那么下次如果是一样的id进行查询就会直接返回空对象。
    优点在于实现简单,维护容易,缺点在于内存浪费。
  • 直接拉黑恶意请求的IP,对方可能不断更换IP
  • 对参数合法性进行校验
  • 布隆过滤器,优点在于内存占用少,不会出现多余的key,缺点在于实现不容易,并且有误判的可能

什么是布隆过滤器

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

布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难,原因是因为对于不同的数据,可能会出现使用同一个hash函数得出相同的值,导致某个bit位上被置为一。从而出现误判。

也就是说布隆过滤器不是准确的,是纯在误差的。

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

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

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

从上面可以知道,布隆过滤器的空间复杂度为O(length),查询和插入的事件复杂度为O(k),其中k为散列函数的个数。

还有一个特点是布隆过滤器是不支持删除数据的,因为删除一个位的数据,可能会影响其他也映射到了这个位的数据。所以布隆过滤器会随着使用,误差率越来越大,需要考虑一个方法去解决这个问题。

布隆过滤器的实现

根据上面的了解,布隆过滤器的实现依靠的是二进制向量以及多个Hash函数,因此,对于布隆过滤器的准确度,其影响因素包含Hash函数的随机性,二进制向量的大小。

目前比较常用的布隆过滤器实现有Guava和Redisson。

对于使用Guava,方法如下

1、添加Maven依赖
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>31.0.1-jre<</version>
</dependency>
2、创建布隆过滤器
BloomFilter<Integer> filter = BloomFilter.create(
  //Funnel 是一个接口,用于将任意类型的对象转换为字节流,
  //以便用于布隆过滤器的哈希计算。
  Funnels.integerFunnel(), 
  10000,  // 插入数据条目数量
  0.001  // 误判率
);

对于使用Redission

1、添加Maven依赖
<dependency>
   <groupId>org.redisson</groupId>
   <artifactId>redisson</artifactId>
   <version>3.16.1</version>
</dependency>
2、配置 Redisson 客户端
@Configuration
public class RedissonConfig {
 Bean
 public RedissonClient redissonClient() {
    Config config = new Config();
    config.useSingleServer().setAddress("redis://localhost:6379");
    return Redisson.create(config);
 }
}
3、初始化
RBloomFilter<Long> bloomFilter = redissonClient.
                                      getBloomFilter("myBloomFilter");
//10000表示插入元素的个数,0.001表示误判率
bloomFilter.tryInit(10000, 0.001);
//插入4个元素
bloomFilter.add(1L);
bloomFilter.add(2L);
bloomFilter.add(3L);
bloomFilter.add(4L);
4、判断数据是否存在
public boolean mightcontain(Long id) {
    return bloomFilter.contains(id);
}

其实Java原生也提供了布隆过滤器的实现方式,Java提供了我们进行位运算的集合BitSet,我们可以使用BitSet以及自己实现Hash函数的方式自己实现一个布隆过滤器。

解决不支持删除的问题

对于上面说到的,随着布隆过滤器的使用,会使得查询的误差越来越大,那么有什么办法解决这种问题?

先说一个简单的,就是直接替换这个布隆过滤器。

我们可以编写一个定时任务,在一定事件之后,创建一个新的布隆过滤器,然后把数据库中的全量数据查询出来然后使用映射将其映射到新的布隆过滤器中,之后无感更新原有的布隆过滤器的指针即可。这种方式实现简单。

还有一种方式是使用计数的方式,我们知道布隆过滤器的底层使用的是二进制向量,也就是因为这个向量只有01两个值,那么如果多个元素都映射到这个位置,我们将其置为0,就会导致更大的误判,所以,如果我们考虑将二进制向量替换为Byte类型,也就是支持计数,这个位的值不仅仅只有01,那么我们就在映射到这个位置的时候使用 ++ 的方式来递增,如果要删除,我们就 – ,这样子如果这个位为0,也可以判断数据是否存在。

有一个很大的缺点就是,空间浪费增大了,并且效率也下降了。同时,我们还得考虑并发修改问题。


相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
8天前
|
Java
使用IDEA创建项目运行我的第一个JAVA文件输出Helloword
本文介绍了如何使用IDEA(IntelliJ IDEA)创建一个新的Java项目,并运行一个简单的Java程序输出"Hello Word"。文章详细展示了创建项目的步骤,包括选择JDK版本、设置项目名称和路径、创建包和类,以及编写和运行代码。最后,还展示了如何通过IDEA的运行功能来执行程序并查看输出结果。
29 4
使用IDEA创建项目运行我的第一个JAVA文件输出Helloword
|
2月前
|
IDE Java 开发工具
Java系统中的错误码设计问题之为Java项目中的错误消息提供国际化支持如何解决
Java系统中的错误码设计问题之为Java项目中的错误消息提供国际化支持如何解决
35 0
|
2月前
|
Java 应用服务中间件 Windows
【应用服务 App Service】App Service 中部署Java项目,查看Tomcat配置及上传自定义版本
【应用服务 App Service】App Service 中部署Java项目,查看Tomcat配置及上传自定义版本
|
3天前
|
缓存 NoSQL Redis
解决 Redis 缓存穿透问题的有效方法
解决 Redis 缓存穿透问题的有效方法
13 2
|
8天前
|
算法 Java
Java项目不使用框架如何实现限流?
Java项目不使用框架如何实现限流?
19 2
|
20天前
|
缓存 NoSQL Java
瑞吉外卖项目笔记+踩坑2——缓存、读写分离优化
缓存菜品、套餐数据、mysql主从复制实现读写分离、前后端分离
瑞吉外卖项目笔记+踩坑2——缓存、读写分离优化
消息中间件 缓存 监控
81 0
|
3天前
|
缓存 NoSQL 前端开发
16)缓存雪崩、缓存击穿、缓存穿透
16)缓存雪崩、缓存击穿、缓存穿透
9 0
|
2月前
|
缓存 NoSQL Java
【Azure Redis 缓存 Azure Cache For Redis】Redis出现 java.net.SocketTimeoutException: Read timed out 异常
【Azure Redis 缓存 Azure Cache For Redis】Redis出现 java.net.SocketTimeoutException: Read timed out 异常
|
2月前
|
缓存 NoSQL 网络协议
【Azure Redis 缓存】Redisson 连接 Azure Redis出现间歇性 java.net.UnknownHostException 异常
【Azure Redis 缓存】Redisson 连接 Azure Redis出现间歇性 java.net.UnknownHostException 异常
下一篇
无影云桌面