Redis学习专栏(Redis缓存雪崩、击穿、穿透)

一.Redis缓存雪崩

热点数据基本都会去做缓存,一般缓存都是定时任务去刷新,或者是查不到之后去更新的,定时任务刷新就有一个问题:缓存雪崩

概念

大量的key设置了相同的过期时间,导致缓存在同一时刻全部失效,造成瞬时DB请求量大、压力骤增,引起雪崩。

此时,大数据量的请求直接到达数据库,如果没有做熔断策略,其他访问该数据库的接口都无法正常返回,会造成业务中断且短期内无法恢复。

缓存正常获取时:
在这里插入图片描述
缓存同时失效时:
在这里插入图片描述

解决方案

缓存雪崩有三种解决方案:使用锁或队列、设置过期标志更新缓存、为key设置不同的缓存失效时间。

(1)使用锁或队列

一般并发量不是特别多的时候,使用最多的解决方案是加锁排队,伪代码如下:

public object GetProductListNew() {
    int cacheTime = 30;
    String cacheKey = "product_list";
    String lockKey = cacheKey;

    String cacheValue = CacheHelper.get(cacheKey);
    if (cacheValue != null) {
        return cacheValue;
    } else {
        synchronized(lockKey) {
            cacheValue = CacheHelper.get(cacheKey);
            if (cacheValue != null) {
                return cacheValue;
            } else {
                //这里一般是sql查询数据
                cacheValue = GetProductListFromDB(); 
                CacheHelper.Add(cacheKey, cacheValue, cacheTime);
            }
        }
        return cacheValue;
    }
}

加锁排队只是为了减轻数据库的压力,并没有提高系统吞吐量。

假设在高并发下,缓存重建期间key是锁着的,那么1000个请求999个都在阻塞,会导致用户等待超时,用户体验很差。

而且在分布式环境还存在并发问题,可能还要解决分布式锁的问题;因此,在真正的高并发场景下很少使用。

(2)设置过期标志更新缓存

第二种解决方案是:给每一个缓存数据增加相应的缓存标记,记录缓存数据是否失效,如果缓存标记失效,则更新数据缓存,伪代码如下:

//伪代码
public object GetProductListNew() {
    int cacheTime = 30;
    String cacheKey = "product_list";
    //缓存标记
    String cacheSign = cacheKey + "_sign";
    String sign = CacheHelper.Get(cacheSign);
    //获取缓存值
    String cacheValue = CacheHelper.Get(cacheKey);
    if (sign != null) {
   	    //未过期,直接返回
        return cacheValue; 
    } else {
        CacheHelper.Add(cacheSign, "1", cacheTime);
        ThreadPool.QueueUserWorkItem((arg) -> {
            //这里一般是sql查询数据
            cacheValue = GetProductListFromDB(); 
            //日期设缓存时间的2倍,用于脏读
            CacheHelper.Add(cacheKey, cacheValue, cacheTime * 2);                 
        });
        return cacheValue;
    }
}

解释说明

1、缓存标记:记录缓存数据是否过期,如果过期会触发通知另外的线程在后台去更新实际key的缓存;

2、缓存数据:它的过期时间比缓存标记的时间延长1倍,例:标记缓存时间30分钟,数据缓存设置为60分钟。

这样,当缓存标记key过期后,实际缓存还能把旧数据返回给调用端,直到另外的线程在后台更新完成后,才会返回新缓存。

(3)为key设置不同的缓存失效时间

可以给缓存设置过期时间时加上一个随机值时间,使得每个key的过期时间分布开来,不会集中在同一时刻失效。或者设置热点数据永远不过期,有更新操作就直接更新缓存,不设置过期时间。

二.Redis缓存击穿

概念

一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到DB,造成瞬时DB请求量大、压力骤增。

解决方案

(1)设置热点数据永远不过期;

(2)加互斥锁;

多个线程同时去查询数据库,可以在第一个查询数据的线程里用一个互斥锁来上锁,其他线程走到这一步拿不到锁就等着,等第一个线程查询结束了,然后加入缓存,后面的线程进来发现有缓存了就直接走缓存。

/**
     * 互斥锁 针对缓存击穿方案
     * @param key
     * @return
     * @throws InterruptedException
     */
    String mutex(String key) throws InterruptedException {
        String value = redisTemplate.opsForValue().get(key).toString();
        if (value  == null) {
            // 设置3min的超时,防止del操作失败的时候,下次缓存过期一直不能load db
            if (redisTemplate.opsForValue().setIfAbsent(key+"_mutex","1",3,TimeUnit.MINUTES)) {
                // 数据库获取值
                value = db.get(key);
                redisTemplate.opsForValue().set(key, value);
                redisTemplate.delete(key+"_mutex");
            } else {
                //其他线程休息50毫秒后重试
                Thread.sleep(50);
                redisTemplate.opsForValue().get(key);
            }
            return value;
        } else {
            return value;
        }
    }

三.Redis缓存穿透

概念

用户不断发起请求,请求的数据在缓存和数据库中都没有,比如数据库的 id 都是1开始自增上去的,发起为id值为 -1的数据请求或id为特别大不存在的数据时,该请求会直接访问数据库,从而导致数据库压力过大,严重时甚至会击垮数据库。

解决方案
  • 接口层增加校验,如id做基础校验,id<=0的直接拦截;

  • 从缓存和数据库中都没有取到的数据,可以将key-value对写为key-null,设置较短的缓存有效时间,如30秒(设置太长会导致正常情况也没法使用),就可以防止攻击用户反复用同一个id进行暴力攻击的情况发生。

参考博客:
1.缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级、缓存热点 key
2.缓存穿透,缓存击穿,缓存雪崩解决方案
3.缓存穿透、缓存击穿、缓存雪崩概念及解决方案
4.Redis-缓存雪崩、击穿、穿透

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

盛夏温暖流年

可以赏个鸡腿吃嘛~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值