Redis 缓存的数据来源于 MySQL数据库,前者作用于内存,后者作用于磁盘,内存访问比磁盘快很多。
那么会出现哪些故障或问题?我们的应对策略是什么?继续往下读吧。
缓存雪崩
Redis 是为了方便快速访问从而缓存的 MySQL 数据库中的数据,那么数据的更新是在 MySQL 数据库中,所以 Redis 要对缓存的数据设置一个过期时间。如果缓存数据过期就会从缓存中移除,用户来访问数据就无法立即命中,会直接访问 MySQL 数据库获得最新数据。最新的数据会缓存到 Redis 中,好让用户下次访问直接命中,这样就让用户既可以快速访问数据,还可以访问到最新的数据。
那如果 Redis 缓存全部失效呢?大量请求访问 MySQL 数据库,不但速度慢,而且还会因为大量请求而崩溃。
我们称之为 缓存雪崩。
那有哪些情况会导致大量请求绕过 Redis 缓存而来到 MySQL 数据库?
- Redis 缓存大量失效。
- Redis 缓存没有失效,但是宕机导致无法提供服务。
大量数据同时失效
(一)随机设置过期时间
避免将大量数据设置相同的过期时间,采用随机数,这可避免大量数据同时失效问题。
(二)互斥锁
如果缓存中的数据有效,但是用户需要的数据不在其中,也就无法命中缓冲,依旧可能会导致大量的请求到 MySQL 数据。为了避免这个问题发生,可以选择加互斥锁。
但业务线程在处理用户请求时,如果发现访问的数据不在 Redis 中,就加个互斥锁,保证同一时间内只有一个请求来构建缓存(从数据库读取数据,再将数据更新到 Redis 里),当缓存构建完后,再释放锁。
实现互斥锁的时候,最好设置超时时间,不然第一个请求拿到了锁,然后这个请求发生了某种意外而一直阻塞,一直不释放锁,这时其他请求也一直拿不到锁,整个系统就会出现无响应的现象。
(三)后台更新缓存
业务线程不再负责更新缓存,缓存也不设置有效期,即“永久有效”,并将更新缓存的工作交由后台行程定时更新。
事实上,缓存数据不设置有效期,并不是意味着数据一直能在内存里,因为当系统内存紧张的时候,有些缓存数据会被“淘汰”,而在缓存被“淘汰”到下一次后台定时更新缓存的这段时间内,业务线程读取缓存失败就返回空值,业务的视角就以为是数据丢失了。
解决上面问题的方式:
- 后台线程不仅负责定时更新缓存,而且也负责频繁地检测缓存是否有效,检测到缓存失效,原因可能是系统紧张而被淘汰,于是马上从数据库读取数据,并更新到缓存。这种方式检测时间不能太长,太长会导致用户获取的数据是一个空值而不是真正的数据,所以检测间隔最好是毫秒级,但是总归是有个间隔时间,用户体验一般。
- 业务线程发现缓存数据失效后,通过消息队列发送一条消息通知后台线程更新缓存,后台线程收到消息后,在更新缓存前可以判断缓存是否存在,存在就不执行更新缓存操作;不存在就读取数据库数据,并将数据加载到缓存。这种方式想必第一种方式缓存的更新会更及时,用户体验也比较好。
Redis 宕机
(一)服务熔断或请求限流机制
服务熔断就是暂停业务应用对缓存服务的访问,直接返回错误,不用再继续访问数据库。等到 Redis 恢复,再允许业务应用访问缓存服务。
但如果就这样处理,会对业务有影响,为避免如此,可以开启请求限流机制,只处理少量的请求,直到 Redis 恢复正常并且预热完后,再解除请求限流的机制。
(二)构建 Redis 缓存高可用集群
如果访问的 Redis 宕机,可以立即选择其他有效的 Redis 服务器进行替代,保证服务不会中断。
缓存击穿
如果某个热点数据过期,此时大量请求来访问(比方说秒杀活动),却无法命中缓存,导致大量请求去到 MySQL 数据库,MySQL 数据库很容易崩溃,这就是缓存击穿。
解决方案可以参考之前的,你可以认为 缓存击穿是 缓存雪崩的一个子集。
互斥锁方案:保证同一时间只有一个业务线程更新缓存,未能获取互斥锁的请求,要么等待锁释放后重新读取缓存,要么就返回空值或者默认值。
不给热点数据设置过期时间:由后台异步更新缓存,或者在热点数据准备要过期前,提前通知后台线程更新缓存以及重新设置过期时间。
缓存穿透
当用户访问的数据,既不在缓存中,也不在数据库中(前面的讨论至少保证数据库是有有效数据,但这里不再是),导致请求在访问缓存时,发现缓存缺失,再去访问数据库时,发现数据库中也有没有要访问的数据,没办法构建缓存数据,来服务后续的请求。那么当有大量这样的请求到来时,数据库的压力骤增,这就是缓存穿透问题。
缓存穿透的发生一般有两种情况:
- 业务误操作,缓存中的数据和数据库中的数据都被误删,导致缓存和数据库中都没有数据。
- 黑客恶意攻击,故意大量访问本不存在数据的业务。
解决方案:
- 非法请求的限制: API 入口处判断请求参数是否合理,是否还有非法数据等,如果存在就直接拒绝接下来的访问。
- 缓存空值或默认值:出现缓存穿透现象,可以针对查询的数据设置一个空值或者默认值,这样后续请求就可以从缓存中读取到控制或者默认值并返回给应用,而不会继续查询数据库。
- 布隆过滤器:快速判断数据是否存在,避免通过查询数据库来判断数据是否存在。
⭐️内容取自《小林Coding》,仅从中取出个人以为需要纪录的内容。不追求内容的完整性,却也不会丢失所记内容的逻辑性。如果需要了解细致,建议访问官方网站。