缓存的另外三个问题(穿透,雪崩,击穿)

一、缓存穿透

1、产生原因

客户端可能会恶意请求一些缓存中和数据库中都不存在的数据,这样缓存永远不会生效,请求都会打到数据库当中,给数据库造成压力。

2、解决方案

a、缓存空对象

思路:对于不存在的数据也可以在缓存中建立缓存,将值设为空,并设置一个较短的TTL时间。

有点:实现简单,维护方便。

缺点:增加额外的内存消耗,短期数据不一致。

可参考一下代码:

public Shop queryWithMutex(Long id){
        String shopJson = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);
        if (StrUtil.isNotBlank(shopJson)){
            Shop shop = JSONUtil.toBean(shopJson, Shop.class);
            return shop;
        }

        if (shopJson != null){
            return null;
        }

        //实现缓存重建
        String lockKey = "lock:shop"+id;
        Shop shop = null;
        try {
            boolean isLock = tryLock(lockKey);
            //判断是否获取成功
            if (!isLock){
                Thread.sleep(50);
                return queryWithMutex(id);
            }
            //如果为空则查询数据库中是否纯在
            shop = getById(id);
            //由于查询数据库比较快,模拟延时
            Thread.sleep(200);
            if (shop == null){
                //店铺不存在
                stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY+id,"", 2, TimeUnit.MINUTES);
                return null;
            }
            //缓存中不存在,存入缓存
            stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY+id,JSONUtil.toJsonStr(shop), 30, TimeUnit.MINUTES);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            //解除锁
            unLock(lockKey);
        }

        return shop;
    }

b、布隆过滤

思路:利用布隆过滤算法,在请求进入redis之前判断是否存在秒如果不存在则直接拒绝请求,

优点:内存占用少。

缺点:实现起来比较复杂,存在误判的可能性。

c、其他

做好数据的基础格式校验:比如让id按照一定规格设定,让恶意用户不容易猜到。

加强用户权限校验。

做好热点参数限流。

二、缓存雪崩

1、产生原因

在同一段时间大量缓存key同时失效或者Redis服务宕机,导致大量请求到达数据库,带来巨大压力。

2、解决方案

给不同的key的TTL添加随机值,避免批量导入后有一段时间会批量失效。

热龙Redis集群提高服务的可用性。

给缓存添加降级限流策略。

给业务添加多级缓存。

三、*缓存击穿

1、产生原因

热点key在某一时间段呗高并发访问,缓存重建的耗时较长。

热点key突然过期,因为重建时间过长,这段时间内大量请求落到数据库上,带来巨大冲击

2、解决方案

a、互斥锁

思路:给线程重建过程加锁,确保重建过程只有一个线程执行,其他线程等待。

优点:实现简单,没有额外消耗内存,一致性好。

缺点:等待导致性能下降,有死锁风险。

b、逻辑过期

思路:热点key缓存永不过期,而是设置一个逻辑过期时间,查询到数据时通过对逻辑过期时间的判断来决定是否需要重建缓存。重建缓存也通过互斥锁保证单线程执行。其他线程无需等待,直接查询旧的数据即可。

优点:线程无需等待性能较好。

缺点:不保证一致性,有额外内存消耗,实现复杂。

Q.E.D.


欢迎来到xuan的空间~