Redis篇:缓存穿透以及解决方案

1.何为缓存穿透

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

比如查询一个id = 0的数据,这是在redis和数据库中肯定不存在的,这样就属于缓存穿透了。

常见的解决方案有两种:

  • 缓存空对象

    • 优点:实现简单,维护方便

    • 缺点:

      • 额外的内存消耗

      • 可能造成短期的不一致

  • 布隆过滤

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

    • 缺点:

      • 实现复杂

      • 存在误判可能

由于布隆过滤器实现相对麻烦,现阶段先试用方案一:缓存空对象  来解决缓存穿透问题:

2.两种解决方案的思路

        缓存空对象思路分析:当我们客户端访问不存在的数据时,先请求redis,但是此时redis中没有数据,此时会访问到数据库,但是数据库中也没有数据,这个数据穿透了缓存,直击数据库,我们都知道数据库能够承载的并发不如redis这么高,如果大量的请求同时过来访问这种不存在的数据,这些请求就都会访问到数据库。

简单的解决方案就是:哪怕这个数据在数据库中也不存在,我们也把这个数据存入到redis中去,这样,下次用户过来访问这个不存在的数据,那么在redis中也能找到这个数据,就不会进入到数据库了

布隆过滤:布隆过滤器其实采用的是哈希思想来解决这个问题,通过一个庞大的二进制数组,走哈希思想去判断当前这个要查询的这个数据是否存在,如果布隆过滤器判断存在,则放行,这个请求会去访问redis,哪怕此时redis中的数据过期了,但是数据库中一定存在这个数据,在数据库中查询出来这个数据后,再将其放入到redis中,

假设布隆过滤器判断这个数据不存在,则直接返回

这种方式优点在于节约内存空间,存在误判,误判原因在于:布隆过滤器走的是哈希思想,只要哈希思想,就可能存在哈希冲突

        

3.基于缓存空值解决缓存穿透问题

接下来我们就拿黑马点评项目做实例,解决商铺查询的缓存穿透问题:

核心思路如下:

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

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

        

我们修改ShopServiceImpl中queryById方法:

/**
     * 根据id查询商铺
     * @param id
     * @return
     */
    @Override
    public Result queryById(Long id) {
        //1.从redis查询商铺缓存
        String key = RedisConstants.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 和 shopJson == ""的情况
        //判断命中的是否为空值 
        if(shopJson != null){
            //只剩下 shopJson == ""的情况,正是我们缓存的空值
            //返回错误信息
            return Result.fail("店铺压根不存在!!!");
        }
        //4.不存在,去数据库查询
        Shop shop = getById(id);
        //5判断数据库中是否存在
        if(shop == null){
            //6.数据库中不存在,返回错误信息,并且缓存空数据
            stringRedisTemplate.opsForValue().set(key,"",RedisConstants.CACHE_NULL_TTL,TimeUnit.MINUTES);
            return Result.fail("店铺不存在!");
        }
        //7.数据库中存在,将商铺信息写入redis,返回商铺信息
        stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop));
        //8.设置过期时间
        stringRedisTemplate.expire(key,RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);
        return Result.ok(shop);
    }

        

4.测试

接下来测试,看看效果是否符合预期:

我们重启服务,然后打开浏览器,访问一个不存在的商铺id = 0:

        

        ​​​​​​  

此时我们去redis控制台去看看,是否已经存入了空值

确实存入了空值。

那么这时候我们再去访问这个id == 0 的商铺,就是直接在redis中返回,而不是打到数据库中了,比如我们测试一下,再一次访问该接口:

        

可以看到errorMsg有所变化,是我们redis中获取到 " " 时,发出的提示

        

说明缓存空值确实实现了,那么就解决了缓存穿透的问题!

5.总结

5.1. 缓存穿透的原因

用户请求的数据在缓存中和数据库中都不存在,不断发起这样的请求,给数据库带来巨大压力,一般是受到了恶意攻击所导致的。

5.2.缓存穿透的解决方案

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

  • 缓存null值

  • 布隆过滤

  • 增强id的复杂度,避免被猜测id规律

相关推荐

  1. redis穿透解决方案

    2024-04-29 00:10:02       6 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-04-29 00:10:02       19 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-04-29 00:10:02       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-04-29 00:10:02       20 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-04-29 00:10:02       20 阅读

热门阅读

  1. Docker 备忘清单(一)

    2024-04-29 00:10:02       13 阅读
  2. ts和js的区别

    2024-04-29 00:10:02       13 阅读
  3. 实验报告4-MyBatis与Spring的整合

    2024-04-29 00:10:02       12 阅读
  4. Fiddlers使用

    2024-04-29 00:10:02       13 阅读
  5. Android Native Hook: 原理、方案对比与具体实现

    2024-04-29 00:10:02       12 阅读
  6. 将mysql转为oracle

    2024-04-29 00:10:02       14 阅读
  7. LeetCode题练习与总结:组合-77

    2024-04-29 00:10:02       12 阅读
  8. new qemu QEMU_OPTION_d

    2024-04-29 00:10:02       14 阅读
  9. 笨蛋学C++【C++基础第八弹】

    2024-04-29 00:10:02       15 阅读
  10. C语言基础—多线程基础

    2024-04-29 00:10:02       12 阅读
  11. YOLOV5 TensorRT部署 BatchedNMS(转换engine模型)(上)

    2024-04-29 00:10:02       11 阅读