缓存穿透基础介绍
缓存穿透是指当缓存系统中无法命中需要的数据时,会直接请求底层存储系统(如数据库),但是如果请求的数据根本不存在,那么大量的请求就会直接穿透缓存层,直接访问底层存储系统,导致底层系统压力过大,甚至崩溃。这也是缓存系统面临的一种常见攻击。
说白了就是查询大量不传在的key 绕过缓存 直接查询数据库 导致缓存跟不不存在一样 这就是缓存穿透
场景介绍
下面说下我的场景 我需要添加一个user 然后我再通过id去查询 如果查询有结果那就放入缓存 如果没有那就直接查询数据库
这是添加user的方法
这是查询user的方法
问题描述
在这两个方法中 我虽然加入的缓存的机制 但是在查询user的方法中 会有一个问题 就是如果我查询的id是不存在的 那就会一直查询数据库
@Cacheable(value = "userCache", key = "#id", condition = "#result != null")
当然你可以说 我去掉注解上面的condition = "#result != null" (这句话意思是condition 条件为真才缓存) 这样就算我查询一个不存在的id 然后将一个null或者空对象返回 然后再加入缓存不就可以啦 下次再去查询这个不存在的id的时候 就不会走数据库了
可是 如果我每一次查询的都是不同的并且不存在的id呢? 那么这个问题还是无法解决 那么接下来就可以引入布隆过滤器 布隆过滤器具体的原理我这里就不做解释了
解决问题
第一步在maven中导入布隆过滤器 事先说明 导入的方法很多 我的方法不一定是最优的 但是一定是能行的
在pom.xml中引入依赖
com.google.guava
guava
30.1-jre
Guava库(Google Guava)是一个Google开发的Java工具库,
它提供了许多常用的Java工具类和数据结构,包括布隆过滤器(Bloom Filter)
创建项目结构 这个不需要解释吧 能看这种文章的 我相信项目结构应该都是能看懂的
AppConfig的代码如下 就是简单的注入Bean对象
@Configuration
public class AppConfig {
@Bean
public BloomFilterService bloomFilterService() {
return new BloomFilterService();
}
}
BloomFilterService的代码如下
public class BloomFilterService {
private BloomFilter bloomFilter;
public BloomFilterService(){
// 创建一个布隆过滤器,设置期望插入的元素数量和误判率
int bloomFilterSize = 1000; // 期望插入的元素数量
double falsePositiveRate = 0.001; // 误判率
bloomFilter = BloomFilter.create(Funnels.longFunnel(), bloomFilterSize, falsePositiveRate);
}
// 添加元素到布隆过滤器
public void add(Long id){
bloomFilter.put(id);
}
//判断元数是否在布隆过滤器中
public boolean contains(Long id){
return bloomFilter.mightContain(id);
}
}
接下来就是在controller中引入并且使用布隆过滤器了
正常注入
@Autowired
private BloomFilterService bloomFilterService;
然后在每一次添加对象之后都将用户的id添加到布隆过滤器中
@CachePut(value = "userCache", key = "#user.id") // value 指定缓存的名字
@PostMapping
public User save(User user){
userService.save(user);
bloomFilterService.add(user.getId());
//这里注意必须要放在添加之后 因为一开始传过来的user是没有id的 会有空指针错误
//这里利用的查询回显 不懂的可以去查查
return user;
}
然后更改查询方法 这样每一次查询user的时候都会先过一遍布隆过滤器 然后再去查询用户 如果存在那么就加入缓存 不传在就会被布隆过滤器拦截 注意去掉, condition = "#result != null" 这个好像跟 布隆有冲突 如果有懂的可以在评论区提一下
@Cacheable(value = "userCache", key = "#id")
@GetMapping("/{id}")
public User getById(@PathVariable Long id){
if (!bloomFilterService.contains(id)) {
// ID 不合法,可以返回错误响应或进行其他处理
return null;
}
User byId = userService.getById(id);
return byId;
}
最后的效果
在添加的时候会将id添加到布隆中 下一次查询的时候如果添加的数据不合法 那就直接拦截
如果添加的数据是合法的 那么就会直接查缓存 如果缓存没有那就直接查数据库 然后再加入缓存
最后遗留一个问题就是 布隆并不是持久的 如果我再第一次启动的时候添加了id123 但是我重启服务之后id123就不在布隆里面了 之后就会被布隆拦截 不过这个就是布隆过滤器持久化的问题了
这个问题我会在下一篇问文章提出一个方法解决
上面的效果我自己测试了发现是没有什么问题的 本人的水平也不高 如果有发现什么问题欢迎评论区讨论 谢谢观看