前言
什么是缓存穿透?
查询一个不存在的数据,mysql查不到数据也会不直接写入缓存,就会导致每次请求都查询数据库
海量用户如果说查询的用户名存在或不存在,全部请求数据库,会将数据库直接打满。
解决方案
1.将DB所有数据放入缓存中
将数据库所有的数据放入缓存中。
存入缓存引出的问题
这里的数据都是永久数据。占用Reids内存太高。所以不采取这个方案。
2.布隆过滤器
下面为流程图
什么是布隆过滤器
位图
所谓位图,就是用每一位来存放某种状态,适用于海量数据,整数,数据无重复的场景。通常是用来判断某个数据存不存在的。
布隆过滤器概念
布隆过滤器是由布隆(Burton Howard Bloom)在1970年提出的 一种紧凑型的、比较巧妙的概率型数据结构,特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”,它是用多个哈希函数,将一个数据映射到位图结构中。此种方式不仅可以提升查询效率,也可以节省大量的内存空间
优点: 内存占用少、没有多余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参数
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)
误判率越低,位数组越长,布隆过滤器占用内存越大;
误判率越低,散列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();
}