缓存雪崩如何随机过期

wen 网络安全 2

本文目录导读:

缓存雪崩如何随机过期

  1. 为什么要随机过期?
  2. 几种随机过期的实现方式
  3. 随机过期的效果量化
  4. 随机过期的注意事项(避坑指南)

缓存雪崩是指大量缓存在同一时间过期(或缓存服务宕机),导致所有请求直接打到数据库,瞬间压垮数据库。随机过期是预防缓存雪崩最常用且有效的策略之一。

核心思路是:不让大量key在同一时刻集体失效,而是给它们的过期时间加上一个随机值,让失效时间均匀分散。


为什么要随机过期?

假设所有缓存key的过期时间都设为 1小时

  • 问题:假如在0:00同时写入了100万个key,那么到1:00时,这100万个key会同时过期
  • 后果:1:00那一刻,大量请求发现缓存不存在,同时去查数据库(缓存雪崩)。

加入随机值后:

  • 每个key的过期时间变为 60分钟 + 随机(0~10分钟)
  • 100万个key的过期时间分布在 60:0070: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 比较平缓,几乎无冲击

随机过期的注意事项(避坑指南)

  1. 随机范围的选择

    • 范围太小:比如基础时间3600秒,随机范围0~5秒,效果不明显,雪崩风险仍在。
    • 范围太大:比如基础时间60秒,随机范围0~600秒,会导致部分key缓存时间异常长。合理的做法:随机范围通常是基础TTL的 5% ~ 30%
  2. 业务敏感度

    • 不适用场景:秒杀倒计时、限时优惠券结束时间,这些场景要求缓存必须在精确时间点失效,不能随机,应改用 互斥锁热点数据永不过期 + 后台更新
  3. 但“随机”不是万能的

    • 随机过期只能解决 “大量key同时过期” 的问题。
    • 无法解决 “Redis服务宕机” 导致的雪崩,宕机需要配合 缓存高可用(主从+哨兵/集群)本地缓存(二级缓存)限流降级 等手段。
  4. 分布式环境下的“时钟偏差”

    • 如果应用服务器时钟不同步,即使代码设置相同过期时间,实际缓存在redis中的TTL也会不同,这在严格意义上反而帮你实现了随机过期,但不应依赖这种“巧合”。
  5. 业务高峰规避

    • 除了随机,还可以配合 “错峰过期”:将不同业务模块的过期时间设定在不同的自然时间段,用户数据在整点过后30分钟过期,订单数据在整点过后45分钟过期。

随机过期是防御缓存雪崩性价比最高的方案:

  • 代码改动:几行代码(base + random)。
  • 效果:将集中冲击分散为平缓流量,数据库压力下降几个数量级。
  • 最佳实践大规模批量缓存写入时务必加随机值,随机范围建议为 [0, 基础TTL * 20%] 秒。

抱歉,评论功能暂时关闭!