16)缓存雪崩、缓存击穿、缓存穿透

简介: 16)缓存雪崩、缓存击穿、缓存穿透


楔子



在使用 Redis 时,会面临缓存雪崩缓存穿透缓存击穿等问题,无论哪一个发生,都会导致大量请求打到数据库。如果数据库宕机,那就是很严重的事故了。

下面我们就来分析一下,这几个问题产生的原因以及解决办法。


缓存雪崩



缓存雪崩是指在短时间内,有大量缓存同时过期,导致大量请求直接查询数据库,从而对数据库造成了巨大的压力,严重情况下可能会导致数据库宕机。这种情况就叫做缓存雪崩。

以上对比图可以看出缓存雪崩对系统造成的影响,那么问题来了,缓存雪崩是如何产生的呢?

  • 缓存中有大量 key 同时过期,导致相应的请求会打到数据库;
  • Redis 实例宕机了;

而问题的解决方式也很简单,首先来看第一种情况。

1)当大量 key 同时过期时。

为了避免缓存同时过期,可在设置缓存时额外添加一个随机时间,这样一来数据的过期时间会有所差别,但差别又不会太大。即避免了大量的缓存同时失效,又能满足业务功能。

除了微调过期时间之外,还可以通过服务降级。而所谓的服务降级就是指,在服务器资源不够、或者说压力过大时,将一些非核心服务暂停,优先保证核心服务的运行。比如:

  • 当业务应用访问的是非核心数据(例如电商商品属性)时,暂时停止从缓存中查询这些数据,而是直接返回预定义信息、空值或是错误信息;
  • 当业务应用访问的是核心数据(例如电商商品库存)时,仍然允许查询缓存,如果缓存缺失,也可以继续通过数据库读取;

这样一来,只有部分过期数据的请求会发送到数据库,数据库的压力就没有那么大了。

另外还设计二级缓存,也就是除了 Redis 之外,再设置一层缓存,当缓存失效之后,先去查询二级缓存。

2)Redis 实例宕机。

实例宕机相比缓存雪崩要更加严重,一般来说一个 Redis 实例可以支持数万级别的请求处理吞吐量,而单个数据库可能只支持数千级别的请求处理吞吐量,它们两个的处理能力至少相差了近十倍。由于 Redis 缓存失效,所以数据库就可能要承受近十倍的请求压力,从而因为压力过大而崩溃。

这个时候,可以进行服务熔断。服务熔断指的是在发生缓存雪崩时,为了防止引发数据库雪崩,甚至是整个系统的崩溃,我们暂停业务应用对缓存系统的接口访问。再具体点说,就是业务应用调用缓存接口时,缓存客户端并不把请求发给 Redis 缓存实例,而是直接返回,等到 Redis 缓存实例重新恢复服务后,再允许应用请求发送到缓存系统。

这样一来,我们就避免了大量请求因缓存缺失,而积压到数据库系统,保证了数据库系统的正常运行。

在业务系统运行时,我们可以监测 Redis 缓存所在机器和数据库所在机器的负载指标,例如每秒请求数、CPU 利用率、内存利用率等。如果我们发现 Redis 缓存实例宕机了,而数据库所在机器的负载压力突然增加(例如每秒请求数激增),说明就发生缓存雪崩了,大量请求被发送到数据库进行处理。我们可以启动服务熔断机制,暂停业务应用对缓存服务的访问,从而降低对数据库的访问压力。

因此服务熔断虽然可以保证数据库的正常运行,但是暂停了整个缓存系统的访问,对业务应用的影响范围大。为了尽可能减少这种影响,我们也可以进行请求限流。也就是在业务系统的请求入口前端,通过加锁排队的方式控制每秒进入系统的请求数,避免过多的请求被发送到数据库。

假设业务系统正常运行时,请求入口前端允许每秒进入系统的请求是 1 万个,其中 9000 个请求都能在缓存系统中进行处理,只有 1000 个请求会被应用发送到数据库进行处理。

然而一旦 Redis 宕机,数据库的每秒请求数会突然增加到每秒 1 万个,此时我们就可以启动请求限流机制,在请求入口前端只允许每秒进入系统的请求数为 1000 个,再多的请求就会在入口前端被直接拒绝服务。所以使用了请求限流,就可以避免大量并发请求压力传递到数据库层。

所以使用服务熔断或是请求限流机制,来应对 Redis 实例宕机导致的缓存雪崩问题,是属于事后诸葛亮。也就是已经发生非常严重的缓存雪崩了(实例宕机了),我们使用这两个机制,来降低雪崩对数据库和整个业务系统的影响。而我们也可以提前预防,也就是通过主从复制的方式,搭建 Redis 高可用集群,主节点挂了就切换到从节点。

所以当发生缓存雪崩时,解决方案如下:

  • 随机化过期时间;
  • 服务降级;
  • 设置二级缓存;
  • 服务熔断(Redis 实例宕机,问题很严重了);
  • 请求限流(相比服务熔断,限流的影响要小一些,它还允许一部分请求过来,交给数据库来处理);
  • 搭建 Redis 集群;


缓存击穿



缓存击穿指的是热点数据在某一时刻失效了,然后有大量的并发请求要访问热点数据,但由于数据已失效,于是这些请求就会全部打到数据库,从而给数据库造成巨大的压力,这种情况就叫做缓存击穿。

缓存击穿的执行流程如下图所示:

它的解决方案有以下两个:

1)加锁排队

此处理方式和缓存雪崩加锁排队的方法类似,都是在查询数据库时加锁排队,以此来减少服务器的运行压力。

但缓存击穿只是热点数据失效,所以我们有更加优雅的方式解决。

2)永不过期

对于某些热点缓存,我们可以设置永不过期,这样就能保证缓存的稳定性。但需要注意:在数据更改之后,要及时更新此热点缓存,不然就会造成查询结果的误差。


缓存穿透



缓存穿透是指查询数据库和缓存都无数据,因为数据库查询无数据,出于容错考虑,不会将结果保存到缓存中。因此每次请求都会去查询数据库,这种情况就叫做缓存穿透。

那么缓存穿透会在什么时候发生呢?

  • 业务层误操作:缓存中的数据和数据库中的数据被误删除了,所以缓存和数据库中都没有数据;
  • 恶意攻击:专门访问数据库中没有的数据;


缓存穿透会给数据库造成很大的压力,而缓存穿透的解决方案有以下几个。

1)缓存空值或缺省值

一旦发生缓存穿透,我们就可以针对查询的数据,在 Redis 中缓存一个空值或是和业务层协商确定的缺省值(例如,库存的缺省值可以设为 0)。后续应用发送请求进行查询时,就可以直接从 Redis 中读取空值或缺省值,然后返回。从而避免把大量请求发送给数据库处理,保证了数据库的正常运行。

但为了提高前台用户的使用体验 (解决长时间内查询不到任何信息的情况),但是我们可以将空结果的缓存时间设置得短一些,例如 3~5 分钟,以防止无用数据过多。

2)使用布隆过滤器

关于布隆过滤器我们后面会说,总之它的特点就是:如果布隆过滤器检测数据存在,那么数据有可能不存在;但如果布隆过滤器检测数据不存在,那么数据一定不存在。

如果数据不存在,那么就不会查询数据库了,这样一来即使发生缓存穿透,也不会影响数据库。布隆过滤器可使用 Redis 实现,本身就能承担较大的并发访问压力。

3)在请求入口的前端进行检测

缓存穿透的一个原因是有大量的恶意请求访问不存在的数据,所以一个有效的应对方案是在请求入口前端,对业务系统接收到的请求进行合法性检测,把恶意的请求(例如请求参数不合理、请求参数是非法值、请求字段不存在)直接过滤掉,不让它们访问后端缓存和数据库。这样一来,也就不会出现缓存穿透问题了。

跟缓存雪崩、缓存击穿这两类问题相比,缓存穿透的影响更大一些。从预防的角度来说,我们需要避免误删除数据库和缓存中的数据;从应对角度来说,我们可以在业务系统中使用缓存空值或缺省值、使用布隆过滤器,以及进行恶意请求检测等方法。


缓存预热



再补充一下缓存预热,首先缓存预热并不是一个问题,而是使用缓存时的一个优化方案,它可以提高前台用户的使用体验。

缓存预热指的是在系统启动的时候,先把查询结果预存到缓存中,以便用户后面查询时可以直接从缓存中读取,节约用户的等待时间。

缓存预热的实现思路有以下三种:

  • 把需要缓存的方法写在系统初始化的方法中,这样系统在启动的时候就会自动的加载数据并缓存数据;
  • 把需要缓存的方法挂载到某个页面或后端接口上,手动触发缓存预热;
  • 设置定时任务,定时自动进行缓存预热;


小结



缓存雪崩、缓存击穿、缓存穿透三者都比较类似,缓存雪崩是大量的 key 同时失效,导致请求全部访问数据库;而缓存击穿是某个 key、只不过是热点 key 失效了,同样导致大量请求访问数据库;缓存穿透是大量请求访问不存在的 key,导致数据库压力增大。

因此这三者是比较相似的,它们的解决方案如下:

最后再说一下,服务熔断、服务降级、请求限流这些方法都是属于有损方案,在保证数据库和整体系统稳定的同时,会对业务应用带来负面影响。

如使用服务降级时,数据的部分就只能得到错误返回信息,无法正常处理。果使用了服务熔断,那么整个缓存系统的服务都被暂停了,影响的业务范围更大。而使用了请求限流机制后,整个业务系统的吞吐率会降低,能并发处理的用户请求会减少,会影响到用户体验。

所以尽量还是提前做好准备,防患于未然。


本文参考自:

  • 极客时间蒋德钧:《Redis 核心技术与实战》
相关文章
|
2月前
|
缓存 NoSQL Java
SpringBoot整合Redis、以及缓存穿透、缓存雪崩、缓存击穿的理解分布式情况下如何添加分布式锁 【续篇】
这篇文章是关于如何在SpringBoot应用中整合Redis并处理分布式场景下的缓存问题,包括缓存穿透、缓存雪崩和缓存击穿。文章详细讨论了在分布式情况下如何添加分布式锁来解决缓存击穿问题,提供了加锁和解锁的实现过程,并展示了使用JMeter进行压力测试来验证锁机制有效性的方法。
SpringBoot整合Redis、以及缓存穿透、缓存雪崩、缓存击穿的理解分布式情况下如何添加分布式锁 【续篇】
|
2天前
|
存储 缓存 NoSQL
解决Redis缓存击穿问题的技术方法
解决Redis缓存击穿问题的技术方法
14 2
|
2天前
|
缓存 NoSQL Redis
解决 Redis 缓存穿透问题的有效方法
解决 Redis 缓存穿透问题的有效方法
11 2
|
2月前
|
缓存 NoSQL Java
SpringBoot整合Redis、以及缓存穿透、缓存雪崩、缓存击穿的理解、如何添加锁解决缓存击穿问题?分布式情况下如何添加分布式锁
这篇文章介绍了如何在SpringBoot项目中整合Redis,并探讨了缓存穿透、缓存雪崩和缓存击穿的问题以及解决方法。文章还提供了解决缓存击穿问题的加锁示例代码,包括存在问题和问题解决后的版本,并指出了本地锁在分布式情况下的局限性,引出了分布式锁的概念。
SpringBoot整合Redis、以及缓存穿透、缓存雪崩、缓存击穿的理解、如何添加锁解决缓存击穿问题?分布式情况下如何添加分布式锁
|
2月前
|
缓存 数据库
缓存穿透和击穿
【8月更文挑战第16天】
35 0
缓存穿透和击穿
|
2月前
|
缓存 NoSQL Redis
一天五道Java面试题----第九天(简述MySQL中索引类型对数据库的性能的影响--------->缓存雪崩、缓存穿透、缓存击穿)
这篇文章是关于Java面试中可能会遇到的五个问题,包括MySQL索引类型及其对数据库性能的影响、Redis的RDB和AOF持久化机制、Redis的过期键删除策略、Redis的单线程模型为何高效,以及缓存雪崩、缓存穿透和缓存击穿的概念及其解决方案。
|
2月前
|
存储 缓存 NoSQL
基于SpringBoot+Redis解决缓存与数据库一致性、缓存穿透、缓存雪崩、缓存击穿问题
这篇文章讨论了在使用SpringBoot和Redis时如何解决缓存与数据库一致性问题、缓存穿透、缓存雪崩和缓存击穿问题,并提供了相应的解决策略和示例代码。
64 0
|
18天前
|
canal 缓存 NoSQL
Redis缓存与数据库如何保证一致性?同步删除+延时双删+异步监听+多重保障方案
根据对一致性的要求程度,提出多种解决方案:同步删除、同步删除+可靠消息、延时双删、异步监听+可靠消息、多重保障方案
Redis缓存与数据库如何保证一致性?同步删除+延时双删+异步监听+多重保障方案
|
2月前
|
缓存 NoSQL Redis
【Azure Redis 缓存】Redission客户端连接Azure:客户端出现 Unable to send PING command over channel
【Azure Redis 缓存】Redission客户端连接Azure:客户端出现 Unable to send PING command over channel
|
2月前
|
缓存 NoSQL Java
Redis深度解析:解锁高性能缓存的终极武器,让你的应用飞起来
【8月更文挑战第29天】本文从基本概念入手,通过实战示例、原理解析和高级使用技巧,全面讲解Redis这一高性能键值对数据库。Redis基于内存存储,支持多种数据结构,如字符串、列表和哈希表等,常用于数据库、缓存及消息队列。文中详细介绍了如何在Spring Boot项目中集成Redis,并展示了其工作原理、缓存实现方法及高级特性,如事务、发布/订阅、Lua脚本和集群等,帮助读者从入门到精通Redis,大幅提升应用性能与可扩展性。
60 0