Redis 缓存穿透击穿和雪崩

Posted 宁川

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis 缓存穿透击穿和雪崩相关的知识,希望对你有一定的参考价值。

1. 缓存穿透

1.1 概念

请求的 key 在缓存和数据源中都不存在,就会导致每次请求都访问到数据源,失去了缓存的意义。

1.1.1 示例代码
@Override
public Goods searchArticleById(Long goodsId){
    Object object = redisTemplete.opsForValue().get(String.valueOf(goodsId));
    
    //缓存查询命中
    if(object != null){
    	return Goods(object);
    }
    
    //从数据源中查询
    Goods goods = goodsMapper.selectByPrimaryKey(goodsId);
    if(goods != null){
        //将查询结果放入缓存
        redisTemplete.opsForValue().set(String.valueOf(goodsId),goods,60,TimeUnit.MINUTES);
    }
    return goods;
}

1.2 解决方案

1.2.1 缓存空值

如果一个查询没有从数据源获取到数据,不管它是真的不存在还是存在故障,我们都把这个空结果进行缓存,但是要设置一个较短的过期时间。这样在短时间内,再次查询就不会继续去访问数据源了。

1.2.1.1 实例代码
@Override
public Goods searchArticleById(Long goodsId){
    Object object = redisTemplete.opsForValue().get(String.valueOf(goodsId));
    
    //缓存查询命中
    if(object != null){
    	return Goods(object);
    }
    
    //从数据源中查询
    Goods goods = goodsMapper.selectByPrimaryKey(goodsId);
    if(goods != null){
         //将查询结果放入缓存
        redisTemplete.opsForValue().set(String.valueOf(goodsId),goods,60,TimeUnit.MINUTES);
    }else{
        //缓存空值
        redisTemplete.opsForValue().set(String.valueOf(goodsId),goods,60,TimeUnit.SECONDS);
    }
        
    return goods;
}
1.2.2 布隆过滤器

将所有可能请求的数据放入一个足够大 bitmap 中,一个不存在的请求数据就会被拦截到,避免对数据源的冲击。

1.2.2.1 什么是布隆过滤器

布隆过滤器(Bloom Filter)是 1970 年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。

如果想判断一个元素是不是在一个集合里,一般想到的是将集合中所有元素保存起来,然后通过比较确定。链表、树、散列表(又叫哈希表,Hash table)等等数据结构都是这种思路。但是随着集合中元素的增加,我们需要的存储空间越来越大。同时检索速度也越来越慢,上述三种结构的检索时间复杂度分别为:O (n), O (log n), O (n/k)。

布隆过滤器的原理是,当一个元素被加入集合时,通过 K 个 Hash 函数将这个元素映射成一个位数组中的 K 个点,把它们置为 1。检索时,我们只要看看这些点是不是都是 1 就(大约)知道集合中有没有它了:如果这些点有任何一个 0,则被检元素一定不在;如果都是 1,则被检元素很可能在。这就是布隆过滤器的基本思想。

1.2.2.2 布隆过滤器的实现

在实际应用当中,我们不需要自己去实现 BloomFilter。可以使用 Guava 提供的相关类库即可。

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>25.1-jre</version>
</dependency>
public class Test1 {

    private static int size = 1000000;

    //定义错误率
    private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), size,0.01);

    public static void main(String[] args) {
        for (int i = 0; i < size; i++) {
            bloomFilter.put(i);
        }

        long startTime = System.nanoTime(); // 获取开始时间
        //判断这一百万个数中是否包含29999这个数
        if (bloomFilter.mightContain(29999)) {
            System.out.println("命中了");
        }
        long endTime = System.nanoTime();   // 获取结束时间
        System.out.println("程序运行时间: " + (endTime - startTime) + "纳秒");
    }

}

布隆过滤器本身存在一定的不准确性。

1.2.3 接口限流与熔断、降级

在一定时间流量超出预期后,拒绝预期的访问,给予用户友好提示。

2. 缓存击穿

2.1 概念

某一个 key 访问流量非常大,大并发集中对这个 key 进行发问,当这个 key 失效的瞬间,持续的大并发直接落到了数据源上,对数据源造成冲击。

2.2 解决方案

2.2.1 热点键永不过期

如果可以话,热点键不设置过期时间,这样也就不存在击穿的问题。

2.2.2 互斥锁

当缓存失效的时候,使用互斥锁来让请求进行排队。这样并没有提供并发量,只是解决了可能会造成的数据源的压力。

2.2.2.1 示例代码
@Override
public Goods searchArticleById(Long goodsId){
    Object object = CacleUtils.get(String.valueOf(goodsId));
    
    //缓存查询命中
    if(object != null){
    	return Goods(object);
    }
    
    //先尝试获取一把分布式锁
    Goods goods = null;
    try{
        Boolean result = ReenLock.tryLock(key_mutex,requestId,60000);
        if(result){
            //从数据源中查询
            goods = goodsMapper.selectByPrimaryKey(goodsId);
            if(goods !=null){
                CacheUtils.set(String.valueOf(goodsId),goods,60000);
            }
        }else{
            //稍后再去尝试
            Thread.sleep(100);
            result = searchArticleById(goodsId);
        }
    }finally{
        ReenLock.unLock(key_mutex,requestId);
    }
    
    return goods;
}

3. 缓存雪崩

3.1 概念

缓存雪崩,是指在某一个时间段,缓存集中过期失效。

3.2 解决方案

3.2.1 加锁排队

使用互斥锁,让来进来的请求进行排队处理。

3.2.2 不同过期时间

既然缓存雪崩时同一时间点,大量的key过期,导致请求落到数据源上,那就尽量让不同的键设置不同的过期时间,尽量让缓存失效的时间分布均匀。

3.2.3 双层缓存策略

C1 为原始缓存,C2 为拷贝缓存,C1 失效时,可以访问 C2,C1 缓存失效时间设置为短期,C2 设置为长期。

3.2.4 数据预热

解决冷启动造成的雪崩,可以提前预估热点数据,将相关的缓存数据直接加载到缓存系统,避免冷启动。

3.2.5 热点永不过期

不过期自然也就没有这个问题。

以上是关于Redis 缓存穿透击穿和雪崩的主要内容,如果未能解决你的问题,请参考以下文章

Redis缓存击穿,缓存穿透,缓存雪崩解决方案(附代码)

Redis缓存击穿,缓存穿透,缓存雪崩解决方案(附代码)

Redis缓存穿透缓存击穿缓存雪崩的原理和解决办法

Redis缓存穿透缓存击穿缓存雪崩的原理和解决办法

REDIS12_缓存雪崩缓存穿透基于布隆过滤器解决缓存穿透的问题缓存击穿基于缓存击穿工作实际案例

REDIS12_缓存雪崩缓存穿透基于布隆过滤器解决缓存穿透的问题缓存击穿基于缓存击穿工作实际案例