
1) 【一句话结论】在游戏排行榜系统中,通过动态调整缓存过期时间(结合数据更新频率)、对热点key加分布式锁/预加载、使用布隆过滤器/空值缓存,并辅以多级缓存(如Redis+Memcached)和预加载策略,可有效避免Redis缓存雪崩、击穿、穿透风险,保障系统稳定与性能。
2) 【原理/概念讲解】
3) 【对比与适用场景】
| 问题类型 | 定义 | 核心原因 | 解决方案 | 适用场景 | 注意点 |
|---|---|---|---|---|---|
| 缓存雪崩 | 大量缓存同时失效,导致DB压力激增 | 缓存统一过期时间,或系统故障导致大量缓存失效 | 随机过期时间、过期时间偏移、多级缓存 | 热点数据集中过期(如定时任务清空缓存) | 偏移量需动态调整,避免极端情况集中失效 |
| 缓存击穿 | 热点key失效,所有请求直接访问DB | 热点key突然失效,无缓存 | 互斥锁、预加载、热点key预热 | 排行榜TOP1用户、活动奖品等高频访问key | 锁粒度尽量小,避免影响其他用户;锁过期时间设10-30秒,防止死锁 |
| 缓存穿透 | 查询不存在的key,导致无效DB查询 | 查询不存在的key,无缓存 | 布隆过滤器、空值缓存 | 查询不存在的用户ID、恶意攻击请求 | 布隆过滤器需根据用户ID范围调整位数,降低误判率;空值缓存需设置合理过期时间 |
4) 【示例】
# 动态计算偏移量(根据更新频率)
def get_random_offset(update_frequency):
if update_frequency < 60: # 每分钟更新
return random.randint(-30, 30)
elif update_frequency < 1440: # 每小时更新
return random.randint(-300, 300)
else: # 每天更新
return random.randint(-3000, 3000)
expire_time = 3600 + get_random_offset(60) # 1小时±30秒(更新频率高)
redis.setex("user_ranking:1001", expire_time, json.dumps(ranking_data))
import redis
import time
r = redis.Redis()
lock_key = f"lock:user_ranking_top1"
lock_value = str(time.time())
# 尝试获取锁
if r.set(lock_key, lock_value, ex=15, nx=True):
ranking = r.get("user_ranking:1001")
if not ranking:
ranking = query_db("SELECT * FROM user_ranking WHERE user_id=1001")
r.setex("user_ranking:1001", 3600, json.dumps(ranking))
r.delete(lock_key)
else:
# 锁获取失败,重试
time.sleep(1)
retry()
from pybloom import BloomFilter
bloom_filter = BloomFilter(capacity=1000000, error_rate=0.001) # 假设用户ID范围0-100万
# 查询前检查布隆过滤器
if not bloom_filter.contains(user_id):
return "用户不存在"
ranking = query_db("SELECT * FROM user_ranking WHERE user_id=?", user_id)
r.setex(f"user_ranking:{user_id}", 3600, json.dumps(ranking))
5) 【面试口播版答案】
“面试官您好,针对游戏排行榜的Redis缓存问题,我总结三个方案:首先缓存雪崩,是大量缓存同时过期导致DB压力,解决方法是给缓存设置随机过期时间,比如原过期时间±300秒(更新频率低时),或者对热点数据预加载,分散失效时间;其次缓存击穿,是热点key失效后所有请求去DB,解决方法是给热点key加分布式锁(比如用SETNX),锁成功才查DB并缓存,或者提前预加载;最后缓存穿透,是查询不存在的key导致无效查询,解决方法是布隆过滤器过滤,或者缓存空值。比如排行榜TOP1用户用锁保证同一时间只有一个线程查DB,不存在的用户ID用布隆过滤器判断,避免无效查询。这样就能避免风险。”
6) 【追问清单】
7) 【常见坑/雷区】