redis 缓存雪崩
Redis缓存雪崩指的是在Redis缓存系统中,大量的缓存数据在同一时间过期失效,导致大量原本可以从缓存获取的数据请求直接落到数据库上,造成数据库瞬间压力过大,甚至可能导致数据库崩溃,进而影响整个系统的正常运行。以下从缓存雪崩产生的原因和解决方案两方面进行介绍:
产生原因
- 大量缓存集中过期:在某些业务场景下,可能会批量设置缓存的过期时间,比如在电商大促活动结束后,大量与活动相关的缓存数据同时过期,使得大量请求直接打到数据库。
- 缓存服务器故障:如果Redis集群中某个节点或整个集群出现故障,导致缓存数据全部丢失或不可用,所有请求也会转而涌向数据库。
解决方案
- 分散过期时间:避免大量缓存同时过期,在设置缓存过期时间时,为每个缓存项添加一个随机的过期时间偏移量。例如,原本设置缓存过期时间为60分钟,可以改为在55到65分钟之间随机取值,这样可以让缓存过期时间更加分散,减轻数据库压力。
- 使用互斥锁:当缓存失效时,先获取一个互斥锁(如使用Redis的SETNX命令),只有获取到锁的请求才能去查询数据库并更新缓存,其他请求则等待一段时间后重试从缓存获取数据。这样可以保证在同一时间只有一个请求去查询数据库,避免大量请求同时穿透到数据库。示例代码如下(以Python和Redis为例):
import redis
import time
r = redis.Redis(host='localhost', port=6379, db=0)
def get_data_with_mutex(key):
mutex_key = f'mutex:{key}'
while True:
# 尝试获取互斥锁
if r.setnx(mutex_key, 1):
try:
data = r.get(key)
if not data:
# 缓存不存在,查询数据库
data = get_data_from_db(key)
r.setex(key, 3600, data) # 设置缓存
return data
finally:
# 释放互斥锁
r.delete(mutex_key)
else:
# 未获取到锁,等待重试
time.sleep(0.1)
def get_data_from_db(key):
# 实际从数据库查询数据的逻辑
return f'data for {key}'
- 设置永不过期:对于一些不经常变化的数据,可以设置为永不过期(即不设置过期时间),或者通过其他机制(如消息队列、定时任务等)来异步更新缓存数据。这样可以避免因缓存过期导致的雪崩问题。
- 二级缓存:采用两级缓存架构,如一级缓存使用Redis,二级缓存可以使用本地缓存(如Python的
functools.lru_cache
)。当一级缓存失效时,先从二级缓存获取数据,如果二级缓存也没有,则查询数据库并更新两级缓存。这样可以在一定程度上减少直接对数据库的请求。 - 缓存预热:在系统启动阶段,提前将部分热点数据加载到缓存中,并设置合理的过期时间。这样可以避免系统刚上线时大量缓存未加载,导致请求直接打到数据库。
- 搭建高可用的Redis集群:使用Redis Sentinel或Redis Cluster等方式搭建高可用的Redis集群,确保即使某个节点出现故障,其他节点仍能正常提供缓存服务,减少因缓存服务器故障导致的雪崩问题。同时,配置合适的持久化策略(如RDB和AOF),以便在Redis重启后能够快速恢复缓存数据。