缓存雪崩、缓存击穿和缓存穿透

Redis 缓存的数据来源于 MySQL数据库,前者作用于内存,后者作用于磁盘,内存访问比磁盘快很多。

那么会出现哪些故障或问题?我们的应对策略是什么?继续往下读吧。

读取缓存流程.png

缓存雪崩

Redis 是为了方便快速访问从而缓存的 MySQL 数据库中的数据,那么数据的更新是在 MySQL 数据库中,所以 Redis 要对缓存的数据设置一个过期时间。如果缓存数据过期就会从缓存中移除,用户来访问数据就无法立即命中,会直接访问 MySQL 数据库获得最新数据。最新的数据会缓存到 Redis 中,好让用户下次访问直接命中,这样就让用户既可以快速访问数据,还可以访问到最新的数据。

缓存同步.png

那如果 Redis 缓存全部失效呢?大量请求访问 MySQL 数据库,不但速度慢,而且还会因为大量请求而崩溃。

我们称之为 缓存雪崩。

缓存雪崩.png

那有哪些情况会导致大量请求绕过 Redis 缓存而来到 MySQL 数据库?

  1. Redis 缓存大量失效。
  2. Redis 缓存没有失效,但是宕机导致无法提供服务。

大量数据同时失效

(一)随机设置过期时间

避免将大量数据设置相同的过期时间,采用随机数,这可避免大量数据同时失效问题。

(二)互斥锁

如果缓存中的数据有效,但是用户需要的数据不在其中,也就无法命中缓冲,依旧可能会导致大量的请求到 MySQL 数据。为了避免这个问题发生,可以选择加互斥锁。

但业务线程在处理用户请求时,如果发现访问的数据不在 Redis 中,就加个互斥锁,保证同一时间内只有一个请求来构建缓存(从数据库读取数据,再将数据更新到 Redis 里),当缓存构建完后,再释放锁。

实现互斥锁的时候,最好设置超时时间,不然第一个请求拿到了锁,然后这个请求发生了某种意外而一直阻塞,一直不释放锁,这时其他请求也一直拿不到锁,整个系统就会出现无响应的现象。

(三)后台更新缓存

业务线程不再负责更新缓存,缓存也不设置有效期,即“永久有效”,并将更新缓存的工作交由后台行程定时更新。

事实上,缓存数据不设置有效期,并不是意味着数据一直能在内存里,因为当系统内存紧张的时候,有些缓存数据会被“淘汰”,而在缓存被“淘汰”到下一次后台定时更新缓存的这段时间内,业务线程读取缓存失败就返回空值,业务的视角就以为是数据丢失了。

解决上面问题的方式:

  1. 后台线程不仅负责定时更新缓存,而且也负责频繁地检测缓存是否有效,检测到缓存失效,原因可能是系统紧张而被淘汰,于是马上从数据库读取数据,并更新到缓存。这种方式检测时间不能太长,太长会导致用户获取的数据是一个空值而不是真正的数据,所以检测间隔最好是毫秒级,但是总归是有个间隔时间,用户体验一般。
  2. 业务线程发现缓存数据失效后,通过消息队列发送一条消息通知后台线程更新缓存,后台线程收到消息后,在更新缓存前可以判断缓存是否存在,存在就不执行更新缓存操作;不存在就读取数据库数据,并将数据加载到缓存。这种方式想必第一种方式缓存的更新会更及时,用户体验也比较好。

Redis 宕机

(一)服务熔断或请求限流机制

服务熔断就是暂停业务应用对缓存服务的访问,直接返回错误,不用再继续访问数据库。等到 Redis 恢复,再允许业务应用访问缓存服务。

但如果就这样处理,会对业务有影响,为避免如此,可以开启请求限流机制,只处理少量的请求,直到 Redis 恢复正常并且预热完后,再解除请求限流的机制。

(二)构建 Redis 缓存高可用集群

如果访问的 Redis 宕机,可以立即选择其他有效的 Redis 服务器进行替代,保证服务不会中断。

缓存击穿

缓存击穿.png

如果某个热点数据过期,此时大量请求来访问(比方说秒杀活动),却无法命中缓存,导致大量请求去到 MySQL 数据库,MySQL 数据库很容易崩溃,这就是缓存击穿。

解决方案可以参考之前的,你可以认为 缓存击穿是 缓存雪崩的一个子集。

互斥锁方案:保证同一时间只有一个业务线程更新缓存,未能获取互斥锁的请求,要么等待锁释放后重新读取缓存,要么就返回空值或者默认值。

不给热点数据设置过期时间:由后台异步更新缓存,或者在热点数据准备要过期前,提前通知后台线程更新缓存以及重新设置过期时间。

缓存穿透

当用户访问的数据,既不在缓存中,也不在数据库中(前面的讨论至少保证数据库是有有效数据,但这里不再是),导致请求在访问缓存时,发现缓存缺失,再去访问数据库时,发现数据库中也有没有要访问的数据,没办法构建缓存数据,来服务后续的请求。那么当有大量这样的请求到来时,数据库的压力骤增,这就是缓存穿透问题。

缓存穿透.png

缓存穿透的发生一般有两种情况:

  • 业务误操作,缓存中的数据和数据库中的数据都被误删,导致缓存和数据库中都没有数据。
  • 黑客恶意攻击,故意大量访问本不存在数据的业务。

解决方案:

  1. 非法请求的限制: API 入口处判断请求参数是否合理,是否还有非法数据等,如果存在就直接拒绝接下来的访问。
  2. 缓存空值或默认值:出现缓存穿透现象,可以针对查询的数据设置一个空值或者默认值,这样后续请求就可以从缓存中读取到控制或者默认值并返回给应用,而不会继续查询数据库。
  3. 布隆过滤器:快速判断数据是否存在,避免通过查询数据库来判断数据是否存在。

⭐️内容取自《小林Coding》,仅从中取出个人以为需要纪录的内容。不追求内容的完整性,却也不会丢失所记内容的逻辑性。如果需要了解细致,建议访问官方网站