Redis缓存雪崩怎么解决?随机过期时间设置不管用?
最近在优化项目缓存时遇到个难题:我们用了Redis存热点数据,但发现大量key会在同一时间集中过期。昨天测试时,设置了统一30分钟过期时间的用户信息缓存突然全失效,导致数据库瞬间被打爆。
我试过给过期时间加随机值,像这样:EXPIRE user:123 300 + Math.random()*60,但发现有些key还是在相近时间过期。看监控发现,即使分散了时间,高峰期还是有20%的缓存同时失效。
还有同事建议用双Redis集群做互备,但具体怎么实现呢?比如主库缓存过期时,从库如何接管?另外,缓存更新时要不要加锁?之前用Lua脚本锁的时候,偶尔会出现死锁错误,像这样:
async function getCacheWithLock(key) {
await redis.evalsha(lockScript, 0, key); // 这里偶尔报错
try {
const data = await fetchDataFromDB();
await redis.set(key, data, 'EX', 1800);
} finally {
redis.del(key + ':lock');
}
}
感觉现在方案都不够稳妥,有没有更可靠的雪崩防护方案?
EXPIRE是个精确命令,不是 TTL 随机值。你那句EXPIRE user:123 300 + Math.random()*60只是给客户端拼了个数发过去,Redis 收到的是一个固定时间戳,所以还是可能集中在某个窗口过期。---
### ✅ 正确解决方案如下:
#### 1. **使用 Redis 的 EXPIRE 指令 + 随机 TTL**
不是拼时间戳,而是拼 TTL(单位秒),这样每个 key 的过期时间才是真正的随机。
或者更简洁点,用
SETEX一起设置:---
#### 2. **热点数据永不过期(主动刷新)**
对特别热点的数据,可以设成永不过期,后台异步刷新。比如:
再配合一个定时器,每隔 10 分钟去检查是否需要刷新,这样就不会出现集中失效。
---
#### 3. **缓存层加降级和熔断**
雪崩来了先拦住,别直接打 DB。用 Redis + 本地缓存兜底,比如:
---
#### 4. **缓存更新加锁防击穿**
你那段 Lua 脚本加锁方式是错的,Redis 的 Lua 脚本是原子的,但
del不在脚本里,容易出问题。推荐用 Redis 官方支持的锁方案,比如:---
#### 5. **双集群互备?真没必要,除非你业务真的特别大**
小项目别折腾双集群,维护成本高,而且你得处理一致性、同步、路由逻辑。除非你 QPS 上了几十万,不然别整这种花活。
---
### ✅ 总结
你目前的方案问题在于:
- 过期时间没真正随机
- Lua 加锁逻辑不完整
- 没有兜底机制
拿去改改这几个点,雪崩问题基本就能解决。
> 我以前也踩过这些坑,改完上线后监控曲线稳如老狗。
首先,你说的随机过期时间其实是个不错的思路,但可能你的随机范围还不够大。我的做法是把随机时间设置得更离散一些,比如:
这样可以让key的过期时间分布得更均匀,减少同时失效的概率。
关于双Redis集群互备的方案,其实实现起来不复杂。你可以用一个主Redis存储正常缓存,另一个备用Redis作为“影子”缓存。当主缓存过期时,先查备用缓存;如果备用缓存也没有,则从数据库加载数据,并同时更新主备两个缓存。代码大概是这样的:
至于你提到的Lua脚本锁问题,死锁的情况可能是锁超时设置不合理导致的。建议你在Lua脚本里加上超时时间,避免锁一直占着不释放。改写后的脚本可以这样:
最后再啰嗦一句,除了技术手段,平时也要多关注监控和压测结果,提前发现问题苗头。毕竟预防总是比事后补救更靠谱,你说是不是?