常见的解决缓存穿透问题的解决方案有两种:
缓存空对象和布隆过滤
加入redis缓存之后商铺查询的整个业务流程如下图所示:
如果根据商铺id来查询数据库之后,该商铺id在数据库中不存在则返回404,但是这样有发生缓存穿透的风险,所以需要修改一下,不能直接返回404,改为将空值写入Redis中,修改后的流程图如下:
这样的话,下一次查询同一个不存在的商铺id时,就可以读到这个空对象。但是在缓存查询时,也要判断是不是空值,不是空值才返回商铺信息,再次修改流程图,如下所示:
原始代码:
@override
public Result queryById(Long id){
String key = CACHE_SHOP_KEY + id;
// 1.从redis中查询商铺
String shopJson = stringRedisTemplate.opsForValue().get(key);
// 2.判断缓存中是否存在
if (StrUtil.isNotBlank(shopJson)){
// 3.存在则直接返回
Shop shop = JSONUtil.toBean(shopJson, Shop.class);
return Result.ok(shop);
}
// 4.不存在则查询数据库
Shop shop = getById(id);
// 5.数据库不存在则返回错误信息
if (shop == null) {
return Result.fail("店铺不存在!");
}
// 6.数据库中存在就将商铺信息写入redis
stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);
// 7.返回ok
return Result.ok(shop);
}
修改后的代码:
@override
public Result queryById(Long id){
String key = CACHE_SHOP_KEY + id;
// 1.从redis中查询商铺
String shopJson = stringRedisTemplate.opsForValue().get(key);
// 2.判断缓存中是否存在
if (StrUtil.isNotBlank(shopJson)){
// 3.存在则直接返回
Shop shop = JSONUtil.toBean(shopJson, Shop.class);
return Result.ok(shop);
}
// 判断命中的是否是空值
if (shopJson == null) {
// 返回错误一个错误信息
return Result.fail("店铺信息不存在!");
}
// 4.不存在则查询数据库
Shop shop = getById(id);
// 5.数据库不存在则返回错误信息
if (shop == null) {
// 将空值写入redis中
stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINITES);
// 返回错误信息
return Result.fail("店铺不存在!");
}
// 6.数据库中存在就将商铺信息写入redis
stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);
// 7.返回ok
return Result.ok(shop);
}
在上一次面试中,面试官问我在实现这个功能的时候是怎么来模拟这个缓存穿透的情况的,是用什么框架工具还是什么逻辑来实现的。第一次面试真的被问懵了,因为其实在做这个功能的时候就只是照着视频敲了一遍代码。支支吾吾也没答上来,emo了。
重新翻看了一下这个视频,发现就是实现之后,在浏览器中发送请求简单测试了一下,发送一些不存在的请求,然后在redis当中查看,发现存储了一些空对象,表示功能确实完成了,将这些请求拦截在外了。
可能面试官问我的是借助什么工具或者如何实现逻辑来预防这个缓存穿透的情况吧?
(我也不太清楚,希望早日能找到实习)