【Redis】缓存穿透问题及其解决方案


文章目录

  • 【Redis】缓存穿透问题及其解决方案
  • 1. 缓存穿透概念及原因
  • 2. 解决方案
  • 2.1 缓存空对象
  • 2.1.1 缓存空对象的优缺点
  • 2.1.2 改进代码
  • 2.2 布隆过滤
  • 2.2.1 布隆过滤的优缺点


1. 缓存穿透概念及原因

缓存穿透:客户端请求的数据在 缓存数据库 当中都不存在的情况,这样缓存就形同虚设,请求都会直接打到数据库。请求量过大时容易可能造成数据库的宕机。

缓存穿透通常都是人为恶意制造的,因为前端一般不会出现明显bug,但是后端因为对数据的校验不够仔细或者因为数据库本身设计的一些问题,很容易被别人通过测试工具直接请求接口并携带一些特别的参数,这样就会造成了缓存穿透。


2. 解决方案

缓存穿透的常见解决方案有两种:

  1. 缓存空对象
  2. 布隆过滤

我们先设定一个场景:假设这是一个电商平台,我们通过id去查询店铺信息。没处理缓存穿透问题的代码如下:

@Override
public Result queryById(Long id) {
    //1.从redis查询商铺缓存
    String key = CACHE_SHOP_KEY + id;
    String shopJson = stringRedisTemplate.opsForValue().get(key);
    //2.判断是否存在
    if (StrUtil.isNotBlank(shopJson)) {
        //3.存在,直接返回
        Shop shop = JSONUtil.toBean(shopJson, Shop.class);
        return Result.ok(shop);
    }
    //4.缓存数据不存在,根据id查询数据库
    Shop shop = getById(id);
    //5.不存在,返回错误
    if (shop == null) {
        return Result.fail("商铺不存在");
    }
    stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);
    return Result.ok(shop);
}

接下来解释两种解决方案及其改进代码。


2.1 缓存空对象

缓存空对象的实现流程如图所示:

redis缓存穿透应用 redis如何解决缓存穿透_算法

解释:当客户端发送请求到达redis时,发现redis中无数据,然后请求继续去查询数据库,发现数据库中也没有数据,那么为防止接下来有很多请求都来查询数据库,所以当数据库查询数据为空时,我们给redis中缓存一个空对象并设置一个过期时间TTL,那么接下来携带和第一次请求相同参数的请求就会直接在redis中获得空对象,然后直接命中返回。


2.1.1 缓存空对象的优缺点

优点:

  • 实现简单,维护方便

缺点:

  • 额外的内存消耗(可能会缓存很多的空对象)
  • 可能造成短期的不一致(缓存空对象的时候数据库没有对应数据,缓存之后数据库就有了对应数据,这样redis和数据库就不一致了,只能等待TTL到期再次查询才能保持一致)

2.1.2 改进代码

改进之后的流程图:

redis缓存穿透应用 redis如何解决缓存穿透_数据结构_02

具体代码如下:

@Override
public Result queryById(Long id) {
    //1.从redis查询商铺缓存
    String key = CACHE_SHOP_KEY + id;
    String shopJson = stringRedisTemplate.opsForValue().get(key);
    //2.判断是否存在
    if (StrUtil.isNotBlank(shopJson)) {
        //3.存在,直接返回
        Shop shop = JSONUtil.toBean(shopJson, Shop.class);
        return Result.ok(shop);
    }
    //此时 shopJson 不是为null就是为""
    if (shopJson != null) {
        //为""直接返回错误信息,为null查询数据库
        return Result.fail("商铺不存在");
    }
    //4.缓存数据不存在,根据id查询数据库
    Shop shop = getById(id);
    //5.不存在,返回错误
    if (shop == null) {
        //缓存空值
        stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
        return Result.fail("商铺不存在");
    }
    stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);
    return Result.ok(shop);
    }

仔细比对缓存空对象改进代码和原代码的区别。


2.2 布隆过滤

布隆过滤的实现流程如图所示:

redis缓存穿透应用 redis如何解决缓存穿透_算法_03

解释:客户端的请求首先到达布隆过滤器,布隆过滤器存储的结果是数据库中数据hash之后得到的结果,如果布隆过滤器判断请求的数据不存在那么就一定不存在,如果判断存在那么也不一定真的存在(不同数据hash的结果可能相同)。假设判断数据不存在则直接拒绝请求;如果判断数据存在则放行到redis中,redis中有数据则直接返回;redis中无对应数据则再去访问数据库,如果数据库中也没有数据就再次发生了穿透;如果数据库中有数据则直接缓存数据并返回。


2.2.1 布隆过滤的优缺点

优点:

  • 内存占用较少,没有多余key

缺点:

  • 实现复杂
  • 存在误判可能(不同数据的hash结果可能相同)