Redis缓存雪崩、缓存击穿、缓存穿透 | 青训营笔记

129 阅读5分钟

这是我参与「第五届青训营 」笔记创作活动的第5天

1. 缓存正常

Redis在内存中,具有较高的访问速度,能够拦截流量对数据库的冲击。使得命中了Key的访问不会打到数据库上,数据库就不会有太大的压力。

流程图 (20)

2. 缓存异常

Redis的异常中,大致可以分为两类:拦不住流量、数据不一致(数据不一致是由于缓存和主存的操作顺序导致的,这里我们先不聊这个)。缓存雪崩缓存击穿缓存穿透都是由于Redis没能拦住流量导致的。

image-20230117212354080

2.1 缓存雪崩

如果缓在某一个时刻出现大规模的key失效,那么就会导致大量的请求打在了数据库上面,导致数据库压力巨大,如果在高并发的情况下,可能瞬间就会导致数据库宕机。这时候如果运维马上又重启数据库,马上又会有新的流量把数据库打死。这就是缓存雪崩。

造成缓存雪崩的关键在于同一时间的大规模的key失效,主要有两种可能:

  • 第一种是Redis宕机
  • 第二种可能就是采用了相同的过期时间。

流程图 (21)

解决方法:

  • 1️⃣事前

    • 均匀过期:通过给Key设置不同的过期时间,让其缓存失效尽量均匀,避免同一时间大量Key过期,使流量大量打到数据库上。

    • 分级缓存:设置多级缓存,不同层级设置不同的失效时间。避免所有层同时过期。

image-20230117201309377

  • 热点Key永不过期。

  • 保证Redis缓存的高可用,防止Redis宕机导致缓存雪崩的问题。可以使用 主从+ 哨兵,Redis集群来避免 Redis 全盘崩溃的情况。

  • 2️⃣事中

    • 互斥锁:在缓存失效后,通过互斥锁或者队列来控制读数据写缓存的线程数量,比如某个key只允许一个线程查询数据和写缓存,其他线程等待。这种方式会阻塞其他的线程,此时系统的吞吐量会下降。
    • 使用熔断机制,限流降级。当流量达到一定的阈值,直接返回“系统拥挤”之类的提示,防止过多的请求打在数据库上将数据库击垮,至少能保证一部分用户是可以正常使用,其他用户多刷新几次也能得到结果。
  • 3️⃣事后

    • 启用redis持久化:当服务器重启时,Redis能够加载缓存,避免服务器刚刚启动就被流量冲垮。数据库启动前先加载一部分缓存,这种方法叫做缓存预热

2.2 缓存预热

缓存预热是指系统上线后,提前将相关的缓存数据加载到缓存系统。避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题,用户直接查询事先被预热的缓存数据。

如果不进行预热,那么Redis初始状态数据为空,系统上线初期,对于高并发的流量,都会访问到数据库中,对数据库造成流量的压力。

缓存预热解决方案:

  • 数据量不大的时候,工程启动的时候进行加载缓存动作;
  • 数据量大的时候,设置一个定时任务脚本,进行缓存的刷新;
  • 数据量太大的时候,优先保证热点数据进行提前加载到缓存。

2.3 缓存击穿

缓存击穿跟缓存雪崩有点类似,缓存雪崩是大规模的key失效,而缓存击穿是某个热点的key失效,大并发集中对其进行请求,就会造成大量请求读缓存没读到数据,从而导致高并发访问数据库,引起数据库压力剧增。这种现象就叫做缓存击穿。

流程图 (22)

解决办法类似于缓存雪崩。

  • 热点Key永不过期。这样就不会导致热点Key的请求大量并发打在数据库上。
  • **限制对单个key请求的并发量。**通过互斥锁或者队列来控制读数据写缓存的线程数量。

2.4 缓存穿透

缓存穿透大部分情况是因为恶意大量请求数据库压根不存在的数据导致。因为数据库中根本不存在该数据,所以不会在Redis中缓存,所有的请求压力都会给到数据库。

流程图 (23)

解决方案:

  • 将无效的Key放入Redis中:当出现Redis查不到数据,数据库也查不到数据的情况,我们就把这个key保存到Redis中,设置value="null",并设置其过期时间极短,后面再出现查询这个key的请求的时候,直接返回null,就不需要再查询数据库了。但这种处理方式是有问题的,假如传进来的这个不存在的Key值每次都是随机的,那存进Redis也没有意义。

  • 使用布隆过滤器:如果布隆过滤器判定某个 key 不存在布隆过滤器中,那么就一定不存在,如果判定某个 key 存在,那么很大可能是存在(存在一定的误判率)。于是我们可以在缓存之前再加一个布隆过滤器,将数据库中的所有key都存储在布隆过滤器中,在查询Redis前先去布隆过滤器查询 key 是否存在,如果不存在就直接返回,不让其访问数据库,从而避免了对底层存储系统的查询压力。

如何选择:针对一些恶意攻击,攻击带过来的大量key是随机,那么我们采用第一种方案就会缓存大量不存在key的数据。那么这种方案就不合适了,我们可以先对使用布隆过滤器方案进行过滤掉这些key。所以,针对这种key异常多、请求重复率比较低的数据,优先使用第二种方案直接过滤掉。而对于空数据的key有限的,重复率比较高的,则可优先采用第一种方式进行缓存。