Redis缓存穿透及在使用存null对象解决的案例

什么是redis的缓存穿透:

   查询一个不存在的数据,数据库查询不到数据,也不会存入缓存,每次请求都会去查询数据库。

如果是坏人使用很多的线程并发向这个数据发送请求,可能会导致数据库崩溃

 

解决办法

1.使用布隆过滤器来解决

什么是布隆过滤器

用于检索一个元素是否存在于一个集合中。底层是初始化一个比较大的数组,存放二进制0或者1,当一个key经过三次hash计算后,找到对应数组的下标并将0改为1,三个数组的位置就能找到一个key。但是会产生一定的误判。

在客户端和redis之间添加一个布隆过滤器,客户端请求过来,布隆过滤器去查询这个数据是否存在,不存在就直接拒绝请求,存在则去继续查询redis。

2.存储一个null对象

请求不存在的数据,会缓存null到redis中。

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

缺点:缓存大量的null会导致redis产生额外的内存消耗。(可以给这些null设置一个比较短的TTL)

案例:黑马点评查询店铺缓存

代码实现:

 @Resource
    private StringRedisTemplate stringRedisTemplate;


    @Override
    public Result queryById(Long id) {
        //缓存穿透 使用存null解决
        Shop shop = queryWithPassThrough(id);
        if (shop == null) {
            return Result.fail("店铺不存在!");
        }
        return Result.ok(shop);
    }

    //封装缓存穿透(查询不存在的数据将其以null的形式缓存到redis中)的方法
    public Shop queryWithPassThrough(Long id) {
        String key = CACHE_SHOP_KEY + id; //缓存的key
        //1.从redis中查询商铺缓存
        String shopJson = stringRedisTemplate.opsForValue().get(key);
        //2.判断缓存是否存在 只有shopJson是非空字符串才为true
        if (StrUtil.isNotBlank(shopJson)) {
            //3.存在,直接返回
            return JSONUtil.toBean(shopJson, Shop.class);
        }
        //由于使用缓存null对象解决需要判断是否是""
        if ("".equals(shopJson)) {
            return null;
        }
        //4.不存在,根据id查询数据库
        Shop shop = getById(id);
        //5.数据库不存在,返回错误 (解决缓存穿透)存一个null数据
        if (shop == null) {
            //(解决缓存穿透)存一个null数据 并且设置过期时间防止一直占用内存
            stringRedisTemplate.opsForValue().set(key, "", 2L, TimeUnit.MINUTES);
            return null;
        }
        //6.存在,写入redis 设置超时时间
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop),30L, TimeUnit.MINUTES);
        //7.返回
        return shop;
    }