缓存雪崩产生的原因
- 大量缓存同时过期:在高并发系统中,如果大量的缓存数据设置了相同或相近的过期时间,当这些缓存同时过期时,大量原本由缓存处理的请求会瞬间直接涌向数据库,导致数据库负载急剧增加,甚至可能因不堪重负而崩溃。
- 缓存服务故障:Redis 作为缓存服务,如果因为某些原因(如服务器宕机、网络故障等)出现不可用情况,所有依赖缓存的请求都会转而访问数据库,引发数据库压力剧增,造成雪崩效应。
应对缓存雪崩的方法
- 设置随机过期时间:在设置缓存过期时间时,为每个缓存数据添加一个随机的过期时间偏移量,避免大量缓存同时过期。例如,原本设置过期时间为 60 分钟,可以改为 55 - 65 分钟之间的随机值。这样能分散缓存过期时间,减轻数据库压力。
import random
import redis
r = redis.Redis(host='localhost', port=6379, db=0)
expire_time = 60 * 60 + random.randint(-300, 300)
r.setex('key', expire_time, 'value')
- 使用互斥锁:当缓存失效时,先使用互斥锁(如 Redis 的 SETNX 命令)来保证只有一个请求能去查询数据库并更新缓存,其他请求等待。这样可以防止大量请求同时查询数据库,避免数据库压力过大。
import redis
r = redis.Redis(host='localhost', port=6379, db=0)
lock_key = 'lock_key'
while True:
if r.setnx(lock_key, 1):
try:
# 查询数据库
data = get_data_from_db()
# 更新缓存
r.set('key', data)
finally:
r.delete(lock_key)
break
else:
# 等待一段时间后重试
time.sleep(0.1)
- 二级缓存方案:采用二级缓存结构,例如一级缓存使用 Redis,二级缓存可以使用本地缓存(如 Python 的
functools.lru_cache
)。当一级缓存失效时,先从二级缓存获取数据,如果二级缓存也没有,再查询数据库。这样能在一定程度上减轻数据库压力。
import functools
@functools.lru_cache(maxsize=128)
def get_data():
data = r.get('key')
if data is None:
data = get_data_from_db()
r.set('key', data)
return data
- 缓存预热:在系统上线前,提前将一些热点数据加载到缓存中,并设置合理的过期时间。这样可以避免系统刚上线时大量请求直接冲击数据库,同时也减少了缓存同时过期的可能性。
hot_keys = ['key1', 'key2', 'key3']
for key in hot_keys:
data = get_data_from_db(key)
r.set(key, data)
- 服务熔断与降级:当检测到数据库压力过大时,通过服务熔断机制暂时切断部分请求对数据库的访问,并返回兜底数据(如默认数据或缓存中旧数据),避免数据库被压垮。同时,开启服务降级,关闭一些非核心业务,保证核心业务的正常运行。例如,在 Python 中可以使用
circuitbreaker
库实现服务熔断。
from circuitbreaker import circuit
@circuit(failure_threshold=5, recovery_timeout=60)
def get_data_from_db():
# 数据库查询逻辑
pass