Skip to content
DAILY QUOTE

“ ”

缓存穿透:请求数据在缓存与数据库中均不存在,不断发起请求,请求都会打到数据库上,给数据库带来很大压力

常见的解决方案有两种:

  • 缓存空对象
    • 优点:实现简单,维护方便
    • 缺点:额外的内存消耗、可能造成短期的不一致
  • 布隆过滤
    • 优点:内存占用较少,没有多余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;
}

结果演示:

  1. 发出非法请求
  2. Redis缓存空值,并设置过期时间
  • 第一次进行查询
  • 存入缓存
  1. 再次发出非法请求,不会再查数据库

总结: 缓存穿透的解决方案有哪些?

  • 缓存空对象(被动)
  • 布隆过滤(被动)
  • 增强id的复杂度,避免被猜测id规律(主动)
  • 做好数据的基础格式校验(主动)
  • 加强用户权限校验(主动)
  • 做好热点参数的限流(主动)