永不过期不能防击穿,必须配合后台异步刷新;因其仅表示无物理TTL,业务数据仍需逻辑过期控制与主动更新,否则缓存长期不刷新将导致数据stale。

永不过期本身不能防击穿,必须配合后台异步刷新逻辑,否则缓存永远不更新,数据就 stale 了。
为什么不能只设 SET key value 不加 EX 就算“永不过期”
Redis 的“永不过期”只是指没用 EXPIRE 或 SETEX 设置物理 TTL,但业务上它仍需更新。如果真的一次写入永不改,那商品价格变了、库存扣减了、开关状态翻转了——缓存就彻底失真。所以“永不过期”本质是把过期控制权从 Redis 移交给应用层,靠自己维护逻辑过期时间。
- value 必须结构化,比如 JSON 里带
"expire_at": 1741603200字段,不能只存原始数据 - 读取时要解析这个字段,判断是否已逻辑过期;未过期直接返回,过期则触发刷新
- 绝对不要依赖
KEYS *扫描全库判断过期——生产环境禁用,会阻塞主线程 - 如果用
SCAN,必须限定前缀(如product:hot:)+ 可选标记(如:ttl_hint后缀),避免无效遍历
setIfAbsent 加锁失败后要不要 Thread.sleep 等待?
在互斥锁刷新路径中,拿不到锁的线程要不要等,取决于你对响应延迟和 DB 压力的权衡。等 50ms 再查一次缓存,大概率能拿到刚刷好的新值;但若此时刷新线程卡住或失败,等待只会让请求堆积,反而拖慢整体 P99。
- 推荐做法:不 sleep,直接返回旧 value(前提是它还可用),同时发一个异步告警或埋点记录“刷新延迟”
- 如果业务要求强一致性(比如支付开关),那就不能返回旧值,得走降级或限流,而不是靠 sleep 拖时间
- Spring Boot 中用
@Scheduled刷热 key 时,锁 key 要带业务标识(如lock:hotkey_refresh:product),防止不同前缀任务互相阻塞
后台线程刷新时,set 缓存要不要显式传 TTL?
要,而且必须显式设。即使你走的是“永不过期”策略,每次 set 都得带一个较长的 TTL(比如 24 小时),这是兜底安全机制。
- 防止某次刷新失败后,value 一直滞留且无法被自动清理(比如进程 crash、网络中断导致没删锁也没写新值)
- 避免因 Redis 内存淘汰策略(如
allkeys-lru)误删热点 key——有 TTL 的 key 更不容易被优先淘汰 - 别用
redisTemplate.opsForValue().set(key, value)这种无过期版本,至少写成set(key, value, Duration.ofHours(24)) - 更新前先做一次
get校验,确认旧 value 结构没变(比如 JSON 字段新增了,老解析器会报错)
逻辑过期字段的精度、刷新线程的错峰时机、分布式锁的续期机制——这些细节不写进代码里,光靠“永不过期”四个字,挡不住真正的击穿。


















