redis从入门到进阶第6讲:缓存雪崩击穿穿透场景与解决方案(代码片段)

执梗 执梗     2023-04-09     697

关键词:

本文已收录于专栏
🍅《Redis从入门到进阶》🍅

专栏前言

   本专栏开启,目的在于帮助大家更好的掌握学习Redis,同时也是为了记录我自己学习Redis的过程,将会从基础的数据类型开始记录,直到一些更多的应用,如缓存击穿还有分布式锁以及Redis持久化等。希望大家有问题也可以一起沟通,欢迎一起学习,对于专栏内容有错还望您可以及时指点,非常感谢大家 🌹。

目录

1.缓存的三大问题

   话说学习缓存,缓存雪崩、击穿、穿透肯定是绕不开的一个话题,不仅在开发中我们需要注意这个问题,在面试中更是面试官有关Redis必问的面试题,可以说我们对这三个场景我们必须了解其中的差异,以及各自的解决方案。

2.缓存雪崩

2.1 什么是缓存雪崩?

   缓存雪崩指的是在同一时间,大量的Key同时失效,导致大量的请求直接绕开了我们的Redis直接打到数据库,数据库一下顶不住直接挂彩了,这就是缓存雪崩的场景。
那有的人肯定好奇,咦?怎么会那么巧大量的Key同时失效,考虑一个秒杀场景,比如双十一的时候,我们在晚上十一点集中上架一批商品,此时缓存过期时间设置为一小时,那么一到十二点这批商品的缓存就全部过期了,如果此时成千上万的用户想购买这些商品发送大量请求,就会导致数据库的压力上升从而可能压垮数据库。

  同一时间大量 Key 失效,就会导致Redis直接如同隐身了一样,那对于双十一这种数以千万级别甚至上亿量级的请求来到数据库,那后果就是灾难性的。一旦你一个库被打倒,那么其他的库可能也会收到影响,导致瞬间都挂掉了。你一重启用户又把你干崩,等你真正修好的时候,可能用户早就睡着了,还在心里吐槽一句什么垃圾产品。
  当然,如果Redis宕机了,那么显然也会触发缓存雪崩的情况。

2.如何解决缓存雪崩?

  讨论完了案发场景,那我们该如何解决缓存雪崩了。

  • 过期时间添加随机值。
    那就对症下药嘛,既然你是大量的key同时过期导致的,那我就尽量让你不一起过期,所以我们在批量添加缓存的时候,可以给过期时间添加一个随机值,使得Key过期的时间尽量分散,这样保证缓存不会大面积的同时失效。
  • 进行集群部署
    集群部署的情况下,我们就无须担心某一台Redis宕机导致触发缓存雪崩,也可以将热点数据均匀分布在不同Redis库中,来避免全部失效的问题。
  • 热点数据永不过期
    我们也可以让热点数据永不过期,只进行更新的操作,这也可以避免缓存雪崩。但可能会带来数据不一致的问题。

3.缓存穿透

3.1 什么是缓存穿透?

  缓存穿透指的是对于一些缓存和数据库中都不存在的数据,而用户却不断对该数据进行请求,如果你的数据库甚至没有建立索引,那么数据库还会进行全表扫描,压力更大。每次数据库都需要去进行查,然后查不到,然后又继续查,然后又查不到,然后又…最终数据库卒,这就是缓存穿透。
  此时显然用户是一个攻击者,比如我们数据库id都是从1开始自增的,它可以故意访问一个负数的id,然后不断请求,如果是我们个人搭建的一些小网站,对这种行为没有预防,只需要用 postman 就可以干崩你的网站。

3.2 如何解决缓存穿透?

  我们考虑如何去解决缓存穿透问题。

  • 参数校验
    在场景中我们已经提及,对于一些非法的参数我们一定要进行校验,不合法的参数直接进行返回,比如一些负数的id,还有用户的鉴权校验。参数校验的思想我们一定要根深蒂固,在任何地方都不应该相信前端传来的参数,我们都应当手动判断其是否合法。
  • 缓存空值
    对于一些不存在的对象,我们也可以将其作为key缓存,value设置null,这样用户再次访问时就不会走数据库查询了。当然需要注意的是我们这个key的过期时间我们需要设置短一些。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

@Component
public class CacheNullValue 

    private static final int CACHE_EXPIRATION = 60; // 缓存过期时间,单位为秒

    @Autowired
    private StringRedisTemplate stringRedisTemplate;
	//@NotBlank对参数进行校验
    public String getData(@NotBlank String key) 
        String data = stringRedisTemplate.opsForValue().get(key);
        if (data == null) 
            data = getDataFromDatabase(key);
            if (data == null) 
                // 将空值缓存指定时间
                stringRedisTemplate.opsForValue().set(key, "", CACHE_EXPIRATION);
             else 
                // 将数据缓存指定时间
                stringRedisTemplate.opsForValue().set(key, data, CACHE_EXPIRATION);
            
        
        return data;
    

    private String getDataFromDatabase(String key) 
        // 从数据库中获取数据的逻辑
        System.out.println("查询数据");
        return null;
    

  • 布隆过滤器
    这是一个解决缓存穿透效果最好的办法,但是确定就是代码维护起来比较复杂。它是一种数据结构,主要的作用是用来判断某个元素是否存在在集合内,具有运行速度快占用内存小等优点,但是具有一定的误判率,而且删除困难等问题。但它有一个特点,就是会可能会误报但不会漏报,存在的元素它一定会知道,不存在的元素它可能也会告诉你存在。
    这样我们就可以使用布隆过滤器来对一些数据进行白名单,如果不存在的话我们直接返回null或者失败。
  • 监控IP
    要知道正常的用户,肯定不会同时发大量的非法请求,当请求次数超过某个阈值时,那么我们可以对该用户ip进行拉黑,把他关进小黑屋。

4. 缓存击穿

4.1 什么是缓存击穿?

  接下来最后聊聊缓存击穿,有的同学可能容易把它和缓存穿透搞混,听起来意思很像,这里分析一下我的记法——巴雷特 (当然你也可以记成 AWM )。不知道大家玩过枪战游戏没,巴雷特是一把杀伤力巨大的狙击枪,单点的杀伤力爆炸,哪怕你站在门后都能把你一枪 击穿
  回到正题,缓存击穿有点类型我们刚刚举的小例子,它指的是对于一个非常热门的key在不停地扛着大并发需求,如果这个key瞬间失效了,此时瞬间大并发就直接到达了数据库,数据库瞬间挂彩,这感觉就像大木桶打穿了一个洞的感觉(没错就是巴雷特的感觉 😂)
  缓存击穿一般是两个场景产生,注意我们强调的是并发的场景下:

    1. 该数据没有人查询过,所以缓存中没有,第一次就遭受大并发访问(某些冷门数据)
    1. 缓存中存在,但恰好失效了,大并发访问瞬间落到数据库(热点数据)

4.2 如何解决缓存击穿?

  • 加互斥锁
    由于是并发场景下,我们可以考虑加锁解决该问题,当缓存未命中时,只有持有锁的线程才能进行数据库查询,然后将查询结构写入缓存,其余的线程只能自旋进行等待。
    下面是逻辑的流程图
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisConnectionUtils;
import org.springframework.data.redis.RedisConnectionFailureException;
import java.util.concurrent.locks.ReentrantLock;
@Component
public class RedisCache 
    
    private final static String CACHE_PREFIX = "my_cache_";
    private final static int EXPIRE_TIME_SECONDS = 60;
    private final ReentrantLock lock = new ReentrantLock();
	@Autowired
    private StringRedisTemplate stringRedisTemplate;
    public Object get(@NotBlank String key) 
        String cacheKey = CACHE_PREFIX + key;
        //1.先查询缓存
        ValueOperations<String, String> ops = redisTemplate.opsForValue();
        String value = ops.get(cacheKey);
        //2.缓存未命中
        if (value == null) 
        	//3.尝试获取锁
            lock.lock();
            try 
         		//4.双重校验
                value = ops.get(cacheKey);           
                if (value == null) 
                	//5.查询数据库
                    value = fetchDataFromDatabase(key);
                    //6.写回redis
                    ops.set(cacheKey, value, EXPIRE_TIME_SECONDS);
                    
                
             catch (Exception e) 
                System.out.println("出现异常");
             finally 
            	//释放锁
                lock.unlock();
            
        
        return value;
    

    private String fetchDataFromDatabase(String key) 
        System.out.println("查询数据库");
        return key;
    

需要注意我们这里的场景是单机的情况下,如果是分布式环境的话我们就得使用分布式锁了,这里我们使用synchronizedlock都是可以的。有关分布式锁我们后续文章会进行讲解

  • 热点数据永不过期

同样因为是数据过期的问题,那我们也可以考虑设置热点数据永不过期,当后台更新数据的同时更新缓存中的数据,当然可能会带来数据不一致的问题,适用于不严格要求缓存一致性的场景。

redis缓存击穿,缓存穿透,缓存雪崩,附解决方案

前言在日常的项目中,缓存的使用场景是比较多的。缓存是分布式系统中的重要组件,主要解决在高并发、大数据场景下,热点数据访问的性能问题,提高性能的数据快速访问。本文以Redis作为缓存时,针对常... 查看详情

redis缓存雪崩缓存穿透缓存击穿

Redis缓存雪崩、缓存穿透、缓存击穿Redis缓存过程缓存雪崩解决方案永不过期合理的设置过期时间使用Redis的分布式锁缓存穿透解决方案过滤非法查询缓存空对象布隆过滤器布隆过滤器的新增布隆过滤器的查询布隆过滤器的删除布... 查看详情

缓存雪崩击穿穿透

   缓存雪崩:   为了保证缓存中的数据与数据库中的数据一致性,会给Redis里的数据设置过期时间,当缓存数据过期后,用户访问的数据如果不在缓存里,业务系统需要重新生成缓存,因此就会访问数据库... 查看详情

如何解决redis缓存雪崩击穿与穿透(代码片段)

Redis最常用使用的场景就是作为业务系统的缓存,既然是作为缓存,那么就不免会碰到缓存常见的问题,即雪崩、击穿与穿透,什么是缓存雪崩、击穿与穿透以及如何解决这几个问题呢?今天我们一起来探讨一... 查看详情

redis12_缓存雪崩缓存穿透基于布隆过滤器解决缓存穿透的问题缓存击穿基于缓存击穿工作实际案例(代码片段)

文章目录①.缓存雪崩②.缓存穿透③.在centos7下布隆过滤器2种安装方式④.缓存击穿⑤.高并发的淘宝聚划算案例落地①.缓存雪崩①.问题的产生:缓存雪崩是指缓存数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至... 查看详情

redis12_缓存雪崩缓存穿透基于布隆过滤器解决缓存穿透的问题缓存击穿基于缓存击穿工作实际案例(代码片段)

文章目录①.缓存雪崩②.缓存穿透③.在centos7下布隆过滤器2种安装方式④.缓存击穿⑤.高并发的淘宝聚划算案例落地①.缓存雪崩①.问题的产生:缓存雪崩是指缓存数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至... 查看详情

redis08_缓存雪崩缓存穿透基于布隆过滤器解决缓存穿透的问题缓存击穿基于缓存击穿工作实际案例(代码片段)

文章目录①.缓存雪崩②.缓存穿透③.在centos7下布隆过滤器2种安装方式④.缓存击穿⑤.高并发的淘宝聚划算案例落地①.缓存雪崩①.问题的产生:缓存雪崩是指缓存数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至... 查看详情

redis之缓存穿透击穿雪崩问题与缓存删除淘汰策略(代码片段)

一、缓存问题与解决缓存穿透缓存穿透是指查询缓存和DB中都不存在的数据。缓存穿透示例:publicStationfindProjectStation(LongstationId)//从缓存中查询Stationstation=(Station)redisTemplate.boundHashOps("project_station").get(stationId);if(station==null)// 查看详情

redis之缓存穿透击穿雪崩问题与缓存删除淘汰策略(代码片段)

一、缓存问题与解决缓存穿透缓存穿透是指查询缓存和DB中都不存在的数据。缓存穿透示例:publicStationfindProjectStation(LongstationId)//从缓存中查询Stationstation=(Station)redisTemplate.boundHashOps("project_station").get(stationId);if(station==null)// 查看详情

redis(三十一)-redis夺命三问之缓存穿透&缓存击穿&缓存雪崩

...网打尽,9.9元买不了吃亏,买不了上当。Python从入门到精通❤️2.Python爬虫专栏,系统性的学习爬虫的知识点。9.9元买不了吃亏,买不了上当。python 查看详情

redis的缓存穿透缓存雪崩缓存击穿问题的概念与解决办法(代码片段)

详细介绍了Redis的缓存穿透、缓存雪崩、缓存击穿等问题的概念与解决办法。文章目录1缓存穿透1.1什么是缓存穿透?1.2怎么解决1.3BloomFilter布隆过滤器1.3.1BloomFilter的原理1.3.2BloomFilter的优缺点1.3.3GuavaBloomFilter1.3.4RedisBloomFilter2... 查看详情

redis——缓存穿透缓存击穿缓存雪崩分布式锁(代码片段)

文章目录:1.缓存穿透1.1什么是缓存穿透?1.2缓存穿透的解决方案2.缓存击穿2.1什么是缓存击穿?2.2缓存击穿的解决方啊3.缓存雪崩3.1什么是缓存雪崩?3.2缓存雪崩的解决方案4.分布式锁4.1使用setnx+del实现分布式... 查看详情

redis——缓存穿透缓存击穿缓存雪崩分布式锁(代码片段)

文章目录:1.缓存穿透1.1什么是缓存穿透?1.2缓存穿透的解决方案2.缓存击穿2.1什么是缓存击穿?2.2缓存击穿的解决方啊3.缓存雪崩3.1什么是缓存雪崩?3.2缓存雪崩的解决方案4.分布式锁4.1使用setnx+del实现分布式... 查看详情

redis缓存穿透缓存击穿缓存雪崩的原理和解决办法(代码片段)

Redis缓存穿透、缓存击穿、缓存雪崩的原理和解决办法1.前言2.缓存穿透的解决办法3.缓存击穿解决办法4.缓存雪崩的解决办法1.前言在大数据时代,由于网络请求的并发,导致的数据库I/O开销巨大,所以为了缓解数据库的压力,缓... 查看详情

redis缓存穿透缓存击穿缓存雪崩的原理和解决办法(代码片段)

Redis缓存穿透、缓存击穿、缓存雪崩的原理和解决办法1.前言2.缓存穿透的解决办法3.缓存击穿解决办法4.缓存雪崩的解决办法1.前言在大数据时代,由于网络请求的并发,导致的数据库I/O开销巨大,所以为了缓解数据库的压力,缓... 查看详情

redis缓存雪崩缓存击穿缓存穿透原因,解决方案?(代码片段)

缓存雪崩、缓存击穿、缓存穿透原因,解决方案?缓存雪崩由于设置缓存时,key都采用了相同expire,导致缓存在某刻同时失效,请求全部直到DB,DB瞬时负载过重而雪崩解决方案在原有失效时间基础上增加一个随机值,比如1~5分钟... 查看详情

redis的缓存问题之缓存穿透缓存雪崩缓存击穿(代码片段)

目录一、什么是缓存穿透?二、常见的解决方案有两种:1、缓存空对象2、布隆过滤综上所述三、编码解决商品查询的缓存穿透问题四、缓存雪崩问题及解决思路1、什么是缓存雪崩?五、缓存击穿问题及解决思路 1、... 查看详情

redis——缓存穿透缓存击穿缓存雪崩

一、缓存穿透1、含义缓存穿透是指查询一个缓存中和数据库中都不存在的数据,导致每次查询这条数据都会透过缓存,直接查库,最后返回空。2、解决方案1)缓存空对象就是当数据库中查不到数据的时候,我缓存一个空对象,... 查看详情