【Redis实战篇】利用布隆过滤器解决缓存穿透问题
前言
在日常业务中,我们将数据存放在数据库中,查询时从数据库查询,但是这样效率并不高,为了提高查询效率,我们将数据缓存在Redis中,查询时直接从Redis中查询,由于Redis中的数据是存放在内存中的,所以查询的效率得到大大提升。但从而也会带来一些问题,其中缓存穿透就是其中一个问题。
文章目录
- 什么是缓存穿透?
- 解决方案
- 1.将DB所有数据放入缓存中
- 2.布隆过滤器
- 什么是布隆过滤器
- 布隆过滤器的误判
- 实际应用
- 1.引入依赖
- 1.1引入Redisson依赖
- 1.2 配置Redis参数
- 2.创建布隆过滤器实例
- 3.在代码中使用
- 4. 应用中存在问题
什么是缓存穿透?
查询一个不存在的数据,mysql查不到数据也会直接写入缓存,就会导致每次请求都查询数据库
海量用户如果说查询的用户名存在或不存在,全部请求数据库,会将数据库直接打满。
解决方案
1.将DB所有数据放入缓存中
将数据库所有的数据放入缓存中。
存入缓存引出的问题
这里的数据都是永久数据。占用Reids内存太高。所以不采取这个方案。
2.布隆过滤器
下面为流程图
什么是布隆过滤器
位图
所谓位图,就是用每一位来存放某种状态,适用于海量数据,整数,数据无重复的场景。通常是用来判断某个数据存不存在的。
布隆过滤器概念
布隆过滤器是由布隆(Burton Howard Bloom)在1970年提出的 一种紧凑型的、比较巧妙的概率型数据结构,特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”,它是用多个哈希函数,将一个数据映射到位图结构中。此种方式不仅可以提升查询效率,也可以节省大量的内存空间
布隆过滤器的思想是将一个元素用多个哈希函数映射到一个位图中,因此被映射到的位置的比特位一定为1。
所以可以按照以下方式进行查找:分别计算每个哈希值对应的比特位置存储的是否为零,只要有一个为零,代表该元素一定不在哈希表中,否则可能在哈希表中。
注意:布隆过滤器如果说某个元素不存在时,该元素一定不存在,如果该元素存在时,该元素可能存在,因
为有些哈希函数存在一定的误判
优点: 内存占用少、没有多余key
缺点: 实现复杂,存在误判
布隆过滤器的误判
首先布隆过滤器的误判是可以调整的。
- 布隆过滤器要设置初始容量。容量设置越大,冲突几率越低。
- 布隆过滤器会设置预期的误判值。
其次在使用布隆过滤器时,就需要考虑误判是不是可以接受
例如在用户设置用户名时,“aaaa”在数据库中不存在,但是在布隆过滤器中的结果返回是存在的,那么“aaaa”这个用户名就不能再被注册,这对用户并不会造成实质性的损失,所以这个误判就是可以接受的。
而在有金钱交易的业务场景中,误判就是不能被接受的。
实际应用
1.引入依赖
1.1引入Redisson依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
</dependency>
1.2 配置Redis参数
这个参数因人而异,根据自己的host地址以及密码等配置即可。
2.创建布隆过滤器实例
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 布隆过滤器配置
*/
@Configuration
public class RBloomFilterConfiguration {
@Bean
public RBloomFilter<String> CachePenetrationBloomFilter(RedissonClient redissonClient) {
RBloomFilter<String> cachePenetrationBloomFilter = redissonClient.getBloomFilter("xxx");
cachePenetrationBloomFilter.tryInit(0, 0);
return cachePenetrationBloomFilter;
}
}
这里tryInit有两个核心参数
public boolean tryInit(long expectedInsertions, double falseProbability)
- expectedInsertions:预估布隆过滤器存储的元素长度
- falseProbability:运行的误判率
误判率越低,位数组越长,布隆过滤器占用内存越大;
误判率越低,散列Hash函数越多,计算耗时越长。
3.在代码中使用
private final RBloomFilter<String> userRegisterCachePenetrationBloomFilter;
4. 应用中存在问题
当布隆过滤器中不存在时,代表数据要插入数据库,如果恶意短时间内(毫秒级)发送海量请求,这些请求都要落到数据库,造成数据库压力,此时就要引入分布式锁来解决问题,锁定当前数据进行串行执行,防止恶意请求将数据打到数据库。
RLock lock = redissonClient.getLock(...);
try {
if (lock.tryLock()){
int insert = baseMapper.insert(...);
if (insert < 1){
throw new ClientException(...);
}
userRegisterCachePenetrationBloomFilter.add(...);
return;
}
throw new ClientException(...);
}finally {
lock.unlock();
}