Redis缓存穿透和缓存击穿

57 阅读2分钟

缓存穿透的原理和解决方案

image.png

使用设置空值的方式

image.png

image.png

以上属于被动防范,我们也可以主动采取措施,比如id复杂化,做好数据格式校验,热点参数限流

缓存击穿的原理和解决方案

image.png

1.互斥锁

image.png

线程1先发来请求,并且获取到了互斥锁。这时线程1就可以去查询数据库,接着将查询到的数据缓存到redis中,最后记得释放锁,不然以后别的线程无法访问数据库。如果在线程1获取互斥锁成功并且还未重建缓存、释放互斥锁的时候,线程2的请求到达,那么线程2无法在redis中获取到缓存,无法获取到互斥锁,那么我们就让线程休眠一会,比方休眠50毫秒,再让线程2去查询缓存,如果还是查询不到,那么再重新休眠,再重新查询。

解决方式:

image.png

image.png

image.png

2.逻辑过期

image.png

线程1查询缓存,逻辑时间过期,就获得锁,然后开启线程2去查询MYSQL,重建缓存,最后释放锁。在它释放锁之前呢,有别的线程过来查数据,获取锁失败,返回过期数据

解决方式:

image.png

image.png

    /**
     * 逻辑过期
     * @param id
     * @return
     */
    public Result queryLuojiGuoqi(Long id) {
//        先从缓存中查询
        String s= RedisConstants.CACHE_SHOP_KEY + id;
        String strJson = stringRedisTemplate.opsForValue().get(s);
//        没查到了就
        if(StrUtil.isBlank(strJson)) {
            return Result.fail("没有查到该店铺信息");
        }
//        查到了
        RedisData redisData = JSONUtil.toBean(strJson, RedisData.class);
        Shop shop = JSONUtil.toBean((JSONObject) redisData.getObject(), Shop.class);
        LocalDateTime localDateTime = redisData.getLocalDateTime();
//       判断是否过期
        if(localDateTime.isAfter(localDateTime.now())) {
//               没过期
            return Result.ok(shop);
        }
//       过期了 1.获取锁
        boolean lock = tryLock(RedisConstants.LOCK_SHOP_KEY + id);
//        1.1获取锁成功
        if(lock) {
//            开启独立线程
            CACHE_REBUILD_EXECUTOR.submit(()->{
                try {
//                    重建缓存
                    showShop(id,20L);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                } finally {
//                    释放锁
                    unlock(RedisConstants.LOCK_SHOP_KEY + id);
                }
            });
        }
        return Result.ok(shop);
    }

总结:缓存穿透,防止别人高并发访问不存在的数据,数据库宕机(举例),我们设置空值来解决
缓存击穿,防止热点key失效期间,同时被高并发访问出事(举例),我们互斥锁了解决,或者添加逻辑过期解决。
有区别:互斥锁如果访问的数据不在缓存我们会查找数据库,写入缓存。而逻辑过期是我们提前把热点key存在缓存中,如果缓存没有,就一直找不到