缓存雪崩本质是大量key在同一秒集中过期导致请求穿透数据库;应使用带抖动的setWithJitter方法(random_int+合理jitterRatio)、配合预热与互斥锁,并加强Redis监控与key设计。

缓存雪崩的本质是过期时间扎堆
不是所有缓存失效都叫雪崩,只有大量 key 在同一秒内集中过期,导致请求穿透到数据库,才构成真正风险。Yii 默认用 Cache::set() 时若不指定过期时间,会走组件配置的全局 defaultDuration,一旦这个值固定,又没做分散处理,上线后流量一涨就容易触发雪崩。
Yii 中给 set() 加随机过期时间的实操方式
别在业务层手动写 rand(300, 600) 然后拼进逻辑——这会让缓存命中率不可控,也难维护。正确做法是封装一个带抖动的缓存写入方法:
public function setWithJitter($key, $value, $baseDuration = 3600, $jitterRatio = 0.1)
{
$jitter = (int) ($baseDuration * $jitterRatio);
$duration = $baseDuration + rand(-$jitter, $jitter);
\Yii::$app->cache->set($key, $value, $duration);
}常见错误现象:Cache::set($key, $value, 3600) 在多个服务或定时任务里被重复调用,结果所有 key 都卡在整点过期;$jitterRatio 设太高(比如 0.5)会导致部分缓存寿命过短,频繁回源。
-
$baseDuration建议设为预估平均访问周期的 1.5–2 倍,不是越长越好 - 生产环境避免用
rand(),改用random_int()更安全(PHP 7+) - 如果用 Redis 缓存,注意
redis-cli的TTL命令返回值可能为 -1(永不过期)或 -2(key 不存在),别拿它反推抖动是否生效
配合缓存预热和互斥锁更稳妥
光靠随机过期只能缓解,不能根治。尤其在发布后、大促前,必须主动触发预热。Yii 没内置预热机制,得自己写命令行脚本:
php yii cache/warmup --keys=user:1,user:2,user:100
而当某个 key 刚好过期、又撞上并发请求时,仍可能有多个请求同时查库。这时要用互斥锁兜底:
- 用
Cache::add()尝试写入一个临时锁 key(如lock:user:123),成功者负责查库并写缓存 - 失败者 sleep 50ms 后重试,最多 3 次,避免死等
- 锁 key 的过期时间必须明显短于业务 key,比如业务设 3600s,锁设 30s,防锁残留
Redis 配置和监控要跟上
Yii 的 RedisCache 组件本身不报雪崩预警,得靠外部手段补位:
- 开启 Redis 的
slowlog,关注GET/SET耗时突增(>10ms) - 用
redis-cli --stat观察expired_keys每秒增量,持续 >500 就该查原因 - 别把
maxmemory-policy设成noeviction,否则内存满时写缓存直接失败,错误信息是OOM command not allowed when used memory > 'maxmemory'
最易被忽略的是:缓存 key 的生成逻辑里如果含时间戳或日期,比如 "user_daily_report_".date('Y-m-d'),那每天零点必然全量刷新——这种“伪随机”比不加抖动还危险。


















