缓存穿透:请求数据在缓存与数据库中均不存在,不断发起请求,请求都会打到数据库上,给数据库带来很大压力
常见的解决方案有两种:
- 缓存空对象
- 优点:实现简单,维护方便
- 缺点:额外的内存消耗、可能造成短期的不一致
- 布隆过滤
- 优点:内存占用较少,没有多余key
- 缺点:实现复杂、存在误判可能(有穿透的风险)、无法删除数据
缓存空对象思路:对于缓存与数据库都没有的数据,如果接收到这样的请求,就把这个数据存到Redis中,下次请求再次被访问,就能直接在Redis中找到。
布隆过滤器思路:利用“多重哈希+位图”机制,实现“说没肯定没,说有不一定有”的极速排查,从而将绝对不存在的恶意假数据全部挡在数据库大门之外。
方案实现: 
java
/**
* 根据id查询数据。
*
* 作用:
* 1.缓存穿透;
*
* @param id业务id
* @return处理结果
*/
@Override
public Result queryById(Long id) {
//缓存穿透
Shop shop=queryWithPassThrough(id);
if(shop == null){
return Result.fail("店铺不存在");
}
return Result.ok(shop);
}java
/**
* 使用缓存空对象解决缓存穿透查询。
*
* 作用:
* 1.从Redis查询商铺缓存;
* 2.判断是否存在;
* 3.存在,直接返回;
* 4.判断命中的是否是空值;
* 5.返回一个错误信息;
*
* @param id业务id
* @return处理结果
*/
public Shop queryWithPassThrough(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 shop;
}
//判断命中的是否是空值
if(shopJson!=null){
//返回一个错误信息
return null;
}
//4.不存在,根据id查询数据库
Shop shop=getById(id);
//5.不存在,返回错误
if(shop==null){
//将空值写入Redis
stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL, TimeUnit.MINUTES);
return null;
}
//6.存在,写入Redis
stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop), RedisConstants.CACHE_SHOP_TTL,TimeUnit.MINUTES);
//7.返回
return shop;
}结果演示:
- 发出非法请求
- Redis缓存空值,并设置过期时间
- 第一次进行查询

- 存入缓存

- 再次发出非法请求,不会再查数据库

总结: 缓存穿透的解决方案有哪些?
- 缓存空对象(被动)
- 布隆过滤(被动)
- 增强id的复杂度,避免被猜测id规律(主动)
- 做好数据的基础格式校验(主动)
- 加强用户权限校验(主动)
- 做好热点参数的限流(主动)