冷数据突热需缓存自动续期而非事后响应,Redis 6.2+ 用 GETEX 原子读取并重设 TTL,老版本可用 GETSET 模拟;但续期必须联动击穿防护,未命中时走互斥锁+异步重建,命中才 GETEX 续期。

冷数据爆火时,EXPIRE 命令根本来不及响应
冷数据突然变热(比如一条旧微博被大V转发),原有缓存可能已过期或压根没设过期时间,但更关键的是:你无法预知它何时会火。等监控告警、人工介入再手动 EXPIRE,流量早就打穿数据库了。这不是运维问题,是设计缺陷——缓存策略不能依赖“事后反应”。
真正可行的路径是让缓存自己感知热度,在读取过程中完成续期,且不阻塞请求。
用 GETSET 或 GETEX 实现无锁读+自动续期
Redis 6.2+ 的 GETEX 是最简方案:它能在读取的同时设置新过期时间,原子完成,无需额外加锁。
-
GETEX key EX 300:读取key值,并将 TTL 重置为 300 秒;若 key 不存在或已过期,返回 nil,TTL 不变 - 适用于“只要命中的读请求,就认为当前是热点”的场景,天然适配冷数据突热
- 注意:它不会触发回源逻辑,续期动作必须由业务层配合——即只有在缓存命中时才续期,未命中仍需走重建流程
老版本 Redis 可用 GETSET 模拟,但要多一次判断:
// 伪代码:先 GET,再 SET 带新 TTL(需确保 value 不为空)
String value = redis.get(key);
if (value != null) {
redis.setex(key, 300, value); // 强制刷新 TTL
}
return value;
续期不是万能的,必须配合“重建保护”机制
只续期不重建,等于把过期问题往后拖,一旦首次重建失败或延迟,后续所有续期都无效。所以续期必须和击穿防御联动:
- 缓存未命中时,必须走互斥锁或逻辑过期流程,防止多个线程同时回源
- 续期操作本身不能替代锁——
GETEX再快,也无法阻止第一个未命中请求触发 DB 查询 - 建议组合:未命中走
SETNX lock:key ...+ 异步重建;命中走GETEX续期,且只对 TTL 剩余
容易忽略的陷阱:续期掩盖了真正的热点识别失效
如果所有 key 都无差别地每次读都续期 300 秒,那缓存就退化成“永不过期”,内存压力和一致性风险陡增。续期行为本身必须带条件:
- 只对命中率突增的 key 续期(例如过去 1 分钟内访问频次 > 100 次)
- 只对原本 TTL 较短(如
- 续期后的新 TTL 应阶梯增长(如首次续 300 秒,二次续 600 秒),避免单次事件引发长期驻留
没有热度判断的续期,只是把击穿风险从“过期瞬间”转移到“内存耗尽时刻”。











