本文目录导读:

缓存雪崩是指大量缓存在同一时间过期(或缓存服务宕机),导致所有请求直接打到数据库,瞬间压垮数据库。随机过期是预防缓存雪崩最常用且有效的策略之一。
核心思路是:不让大量key在同一时刻集体失效,而是给它们的过期时间加上一个随机值,让失效时间均匀分散。
为什么要随机过期?
假设所有缓存key的过期时间都设为 1小时。
- 问题:假如在0:00同时写入了100万个key,那么到1:00时,这100万个key会同时过期。
- 后果:1:00那一刻,大量请求发现缓存不存在,同时去查数据库(缓存雪崩)。
加入随机值后:
- 每个key的过期时间变为
60分钟 + 随机(0~10分钟)。 - 100万个key的过期时间分布在
60:00到70:00之间。 - 同一秒内过期的key数量大幅减少,DB压力分散。
几种随机过期的实现方式
在缓存过期时间上直接加随机值(最常用)
设置缓存时,在基础TTL(生存时间)上累加一个随机秒数。
// 伪代码示例
public void setCache(String key, Object value, int baseExpireSeconds) {
// 基础时间 + 随机范围(0 ~ 600秒,即10分钟)
int randomRange = 600;
int expireTime = baseExpireSeconds + RandomUtils.nextInt(0, randomRange);
redisTemplate.opsForValue().set(key, value, expireTime, TimeUnit.SECONDS);
}
- 适用场景:所有批量写入、定期刷新的缓存。
- 优点:实现简单,极难出现大规模同时过期。
- 缺点:如果业务要求某些key必须精确在某个时间失效(如活动倒计时),此方法不适用。
在固定过期时间上加上随机数(适用于定时任务批量写入)
如果缓存是通过定时任务(如凌晨2点统一更新)批量写入的,可以在写入时添加随机延迟。
# Python 示例:批量写入
import random, time
def refresh_all_cache():
for key in all_keys:
# 给每个key的基础过期时间(比如3600秒)加上一个随机偏移
expire = 3600 + random.randint(0, 900) # 随机加0~15分钟
redis.set(key, value, ex=expire)
# 甚至可以在写入时稍微sleep,避免同一瞬间大量写入导致redis链接爆满
# time.sleep(random.random() * 0.1)
双倍过期 + 随机补偿
对于热点数据,有时会设置逻辑过期或物理过期双保险,物理过期时间可以设为正常值的2倍+随机值,然后用后台线程或定时器在逻辑过期时去更新,确保物理过期几乎不会发生。
// Go 示例 // 物理过期时间设为 2小时 + 随机10分钟 expire := 2*3600 + rand.Intn(600) redis.Set(ctx, key, value, time.Duration(expire)*time.Second) // 后台单独维护一个 "逻辑过期时间"(比如1小时),实际缓存仍在,后台异步刷新
使用缓存预热时的“分段加载”
如果涉及大版本更新(如缓存全部重建),不要一次性把所有缓存写进去,而是分批次写入,每批次使用不同的基础过期时间。
第一批:过期时间 = 60分钟 + 随机(0~10分钟)
第二批:过期时间 = 70分钟 + 随机(0~10分钟)
第三批:过期时间 = 80分钟 + 随机(0~10分钟)
...
随机过期的效果量化
| 场景 | 过期时间设置 | 同一秒过期key数量(假设100万key) | 数据库压力 |
|---|---|---|---|
| 无随机 | 固定 3600秒 | ~1,000,000 | 瞬间崩盘 |
| 小随机 | 3600 + 随机(0~300秒) | 约 3,333 | 较大,但可接受 |
| 大随机 | 3600 + 随机(0~1800秒) | 约 555 | 比较平缓,几乎无冲击 |
随机过期的注意事项(避坑指南)
-
随机范围的选择
- 范围太小:比如基础时间3600秒,随机范围0~5秒,效果不明显,雪崩风险仍在。
- 范围太大:比如基础时间60秒,随机范围0~600秒,会导致部分key缓存时间异常长。合理的做法:随机范围通常是基础TTL的 5% ~ 30%。
-
业务敏感度
- 不适用场景:秒杀倒计时、限时优惠券结束时间,这些场景要求缓存必须在精确时间点失效,不能随机,应改用 互斥锁 或 热点数据永不过期 + 后台更新。
-
但“随机”不是万能的
- 随机过期只能解决 “大量key同时过期” 的问题。
- 无法解决 “Redis服务宕机” 导致的雪崩,宕机需要配合 缓存高可用(主从+哨兵/集群)、本地缓存(二级缓存)、限流降级 等手段。
-
分布式环境下的“时钟偏差”
- 如果应用服务器时钟不同步,即使代码设置相同过期时间,实际缓存在redis中的TTL也会不同,这在严格意义上反而帮你实现了随机过期,但不应依赖这种“巧合”。
-
业务高峰规避
- 除了随机,还可以配合 “错峰过期”:将不同业务模块的过期时间设定在不同的自然时间段,用户数据在整点过后30分钟过期,订单数据在整点过后45分钟过期。
随机过期是防御缓存雪崩性价比最高的方案:
- 代码改动:几行代码(
base + random)。 - 效果:将集中冲击分散为平缓流量,数据库压力下降几个数量级。
- 最佳实践:大规模批量缓存写入时务必加随机值,随机范围建议为
[0, 基础TTL * 20%]秒。