Redis雪崩现象通常是指在某个时间点,大量的缓存同时失效(例如由于缓存过期),导致大量请求直接访问数据库,造成数据库的高负载,甚至引发服务崩溃。为了解决这个问题,可以采用多种方案,以下是一些常见的做法:
1. 缓存过期时间设置不一致
避免缓存失效时间相同导致大量缓存失效的情况。通过对缓存过期时间进行随机化来避免缓存“集中失效”。
方案:
给不同的缓存设置不同的过期时间,或者在原有的过期时间基础上随机加上一些浮动时间(例如 +10~30秒),避免缓存失效时集中对数据库的压力。
import random
import time
import redis
def get_cache(key):
r = redis.Redis(host='localhost', port=6379, db=0)
cache_value = r.get(key)
if cache_value is None:
# 如果缓存不存在,可以设置一个随机的过期时间
random_expiry = random.randint(50, 150) # 随机过期时间,单位秒
r.setex(key, random_expiry, fetch_from_db(key)) # 从数据库中获取并设置到缓存
return fetch_from_db(key)
return cache_value
def fetch_from_db(key):
# 模拟数据库查询
return f"DB_value_for_{key}"
2.双重检查机制(或称"互斥锁"机制)
如果缓存失效,可以在短时间内使用互斥锁机制来确保只有一个请求会去加载数据到缓存,而其他请求则等待缓存填充完成。这可以避免多个请求同时访问数据库。
方案:
在缓存失效时,使用Redis的 SETNX 命令进行互斥锁控制,确保只有一个请求去数据库获取数据并更新缓存。
import redis
import time
import threading
# 使用Redis的SETNX来模拟互斥锁
def get_cache_with_lock(key):
r = redis.Redis(host='localhost', port=6379, db=0)
cache_value = r.get(key)
if cache_value is None:
lock_key = f"{key}_lock"
# 尝试设置锁
if r.setnx(lock_key, 1):
# 设置过期时间,防止死锁
r.expire(lock_key, 10) # 锁的过期时间为10秒
# 从数据库获取数据并设置缓存
data = fetch_from_db(key)
r.setex(key, 60, data) # 假设缓存过期时间为60秒
r.delete(lock_key) # 解锁
return data
else:
# 如果其他请求正在处理缓存,可以等待一段时间再尝试
time.sleep(0.1)
return get_cache_with_lock(key)
return cache_value
def fetch_from_db(key):
# 模拟数据库查询
return f"DB_value_for_{key}"
3. 永不过期的缓存 + 定时更新
使用永不过期的缓存(SET)并通过定时任务(如Redis的cron任务)定期刷新缓存数据。这样可以避免缓存因过期造成的大量请求集中访问数据库。
方案:
设置缓存为永不过期(或一个非常长的时间),同时安排后台任务定期刷新缓存内容。
import redis
import time
def update_cache_periodically():
r = redis.Redis(host='localhost', port=6379, db=0)
while True:
# 定时刷新缓存
for key in r.keys():
r.set(key, fetch_from_db(key))
time.sleep(3600) # 每小时刷新一次缓存
def get_cache(key):
r = redis.Redis(host='localhost', port=6379, db=0)
cache_value = r.get(key)
if cache_value is None:
# 如果缓存为空,直接从数据库加载并设置缓存
data = fetch_from_db(key)
r.set(key, data) # 设置一个非常长的过期时间
return data
return cache_value
def fetch_from_db(key):
# 模拟数据库查询
return f"DB_value_for_{key}"
# 定时任务线程启动
import threading
threading.Thread(target=update_cache_periodically, daemon=True).start()
4. 使用缓存预热
在服务启动时,提前将常用的数据加载到Redis缓存中,避免用户请求时缓存为空。
方案:
在系统启动时,手动或自动加载一些热点数据到缓存中,避免首次请求时缓存为空。
import redis
def preload_cache():
r = redis.Redis(host='localhost', port=6379, db=0)
keys_to_preload = ["key1", "key2", "key3"]
for key in keys_to_preload:
r.set(key, fetch_from_db(key)) # 设置缓存
def fetch_from_db(key):
# 模拟数据库查询
return f"DB_value_for_{key}"
preload_cache()
5. 请求限流与熔断
当数据库压力过大时,可以通过限流或熔断机制,保护数据库不受过高的访问压力。
方案:
当缓存失效时,通过限流或熔断机制暂时拒绝一些请求,避免瞬间对数据库造成过大压力。
import time
import random
def request_with_fallback(key):
# 模拟限流,避免瞬间访问压力过大
if random.random() < 0.1: # 模拟10%的请求会触发熔断
print("熔断请求,稍后再试")
return None
return get_cache(key)
def get_cache(key):
# 此处可以集成上面提到的缓存方案
return f"Cached_{key}"