Redis缓存穿透问题的解决思路

Redis缓存穿透问题的解决思路

  • 1.什么是缓存穿透
  • 2.解决方案
    • 2.1 缓存空对象解决方案
    • 2.2 布隆过滤器解决方案
    • 2.3 图解
  • 3. 实际应用
    • 3.1 代码实现
    • 4. 小总结

1.什么是缓存穿透

缓存穿透是指客户端请求的数据在缓冲中和数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据库中。

2.解决方案

常见的解决方案有两种。

  • 缓存空对象
    • 优点:
      • 实现简单,维护方便。
    • 缺点:
      • 额外的内存消耗
      • 可能造成短期的不一致
  • 布隆过滤器
    • 优点:内存占用少,没有多余的key
    • 缺点:实现复杂,存在误判可能。

2.1 缓存空对象解决方案

缓存空对象思路分析: 当我们客户端访问不存在的数据时,先请求redis,但是此时redis中没有数据,此时会访问到数据库,但是数据库中也没有数据,这个数据穿透了缓存,直击数据库,我们都知道数据库能够承载的并发不如redis这么高,如果大量的请求同时过来访问这种不存在的数据,这些请求就都会访问到数据库,简单的解决方案就是哪怕这个数据在数据库中也不存在,我们也把这个数据存入到redis中去,这样,下次用户过来访问这个不存在的数据,那么在redis中也能找到这个数据就不会进入到数据库了。

2.2 布隆过滤器解决方案

布隆过滤: 布隆过滤器其实采用的是哈希思想来解决这个问题,通过一个庞大的二进制数组,走哈希思想去判断当前这个要查询的这个数据是否存在,如果布隆过滤器判断存在,则放行,这个请求会去访问redis,哪怕此时redis中的数据过期了,但是数据库中一定存在这个数据,在数据库中查询出来这个数据后,再将其放入到redis中,
假设布隆过滤器判断这个数据不存在,则直接返回
这种方式优点在于节约内存空间,存在误判,误判原因在于:布隆过滤器走的是哈希思想,只要哈希思想,就可能存在哈希冲突。

2.3 图解

Redis缓存穿透问题的解决思路_第1张图片

3. 实际应用

在原来的逻辑中,我们如果发现这个数据在mysql中不存在,直接就返回404了,这样是会存在缓存穿透问题的

现在的逻辑中:如果这个数据不存在,我们不会返回404 ,还是会把这个数据写入到Redis中,并且将value设置为空,欧当再次发起查询时,我们如果发现命中之后,判断这个value是否是null,如果是null,则是之前写入的数据,证明是缓存穿透数据,如果不是,则直接返回数据。
Redis缓存穿透问题的解决思路_第2张图片

3.1 代码实现

工具类:

	/**
     * 缓存空字符 解决缓存穿透
     * @param keyPrefix
     * @param id
     * @param type
     * @param dbFallback
     * @param time
     * @param unit
     * @param 
     * @param 
     * @return
     */
    public <R,ID> R queryWithPassThrough(
            String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit){
        String key = keyPrefix + id;
        // 1.从redis查询商铺缓存
        String json = stringRedisTemplate.opsForValue().get(key);
        // 2.判断是否存在
        if (StrUtil.isNotBlank(json)) {
            // 3.存在,直接返回
            return JSONUtil.toBean(json, type);
        }
        // 判断命中的是否是空值
        // ""->表示数据库没有 缓存了空字符串  null->表示:缓存中没有,数据库中不一定有没有,需要去查询数据库 ,
        // 所以 json !=null 也就是 = "" 表示 缓存了空字符串 返回null 表示数据库也没有。
        if (json != null) {
            // 返回一个错误信息
            return null;
        }

        // 4.不存在,根据id查询数据库
        R r = dbFallback.apply(id);
        // 5.不存在,返回错误
        if (r == null) {
            // 将空值写入redis
            stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
            // 返回错误信息
            return null;
        }
        // 6.存在,写入redis
        this.set(key, r, time, unit);
        return r;
    }

调用:

    public Result queryShopById(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, 不存在,根据id查询数据库
//        Shop shop = getById(id);
//        // 5.不存在 返回错误
//        if(shop == null ){
//            return Result.fail("店铺不存在!");
//        }
//        //6.存在 写入redis
//        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop),30L, TimeUnit.MINUTES);
//        //7.返回
//        return Result.ok(shop);


        // -- 升级

        // 解决缓存穿透
        Shop shop = cacheClient
                .queryWithPassThrough(CACHE_SHOP_KEY, id, Shop.class, this::getById, CACHE_SHOP_TTL, TimeUnit.MINUTES);
        if (shop == null) {
            return Result.fail("店铺不存在!");
        }
        // 7.返回
        return Result.ok(shop);
    }

代码没有贴全,大概思路是这样。

4. 小总结

缓存穿透产生的原因是什么?

  • 用户请求的数据在缓存中和数据库中都不存在,不断发起这样的请求,给数据库带来巨大压力

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

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

参考资料:
黑马Redis资料。

若有错误,希望大佬指出。
对你有帮助给点个再走呗。

你可能感兴趣的:(NoSql,redis,缓存,数据库)