redis设ttl为-1仅确保key永不过期,但无法解决数据陈旧、无刷新机制、多实例写覆盖三大问题;必须由应用层实现带锁扫描、逻辑过期校验与异步回填的闭环刷新。

Redis 本身不支持“永不过期 + 自动后台刷新”这种组合能力,所有逻辑必须由应用层实现。直接设 TTL 为 -1 只是让 key 不被 Redis 自动删除,但无法解决数据陈旧、一致性丢失、写覆盖或刷新失败静默等问题。
为什么不能只靠 SET key value EX -1?
设永不过期只是第一步,但会立刻暴露三个硬伤:
- 缓存数据一旦写入就再也不会自动更新,业务变更后用户永远看到旧值
- 没有刷新触发机制,你得自己决定“什么时候查库、查哪些 key、失败了怎么办”
- 多个实例同时刷新同一组 key,可能引发数据库压垮或缓存值被低版本覆盖(即“写覆盖”)
换句话说:EX -1 解决的是“不丢”,不是“不旧”。真正在意数据时效性的系统,必须补上异步刷新的闭环逻辑。
如何用后台线程安全地刷新热点 key?
核心是“扫描 + 锁 + 异步回填”,不是轮询全量 key,也不是裸启 new Thread():
- 扫描范围限定在业务前缀下,比如只扫
event:hot:*,禁用KEYS * - 每次扫描前先用
SETNX lock:refresh:event_hot 1 EX 30拿分布式锁,防止集群多实例重复刷 - 对每个待刷新 key,用
GET拿出当前 value,解析其中的logicExpireTime字段(必须存在,不能依赖 Redis 的 TTL) - 若
logicExpireTime ,才触发异步加载:提交到线程池,查 DB,构造新 <code>CacheWrapper,再SET回 Redis - 异步任务内必须做二次校验——写入前再
GET一次,确认没被其他线程抢先更新,避免覆盖
示例关键判断逻辑:
if (wrapper == null || now > wrapper.getLogicExpireTime()) { asyncRefresh(key); }
突发热点事件下最易踩的坑
事件型热点(如明星出轨、政策发布)和日常热点不同:访问不可预测、峰值陡峭、生命周期短。这时候容易犯错:
- 扫描频率设成固定 30 秒——等你扫到,流量已经打穿 DB。应结合监控动态提速,比如当
redis_key_miss_rate{app="api"} > 0.3时,临时把刷新周期压到 5 秒 - 刷新失败不记录日志也不告警,导致某类 event key 连续 2 小时没更新,前端显示“活动未开始”
- 把刷新逻辑写在
@Scheduled方法里却没加@Async或线程池配置,结果整个定时任务卡住,后续周期全部堆积 - value 里没存
logicExpireTime,而是幻想用TTL key判断是否该刷新——但EX -1后TTL永远返回 -1,这个分支永远不进
真正扛住突发热点的系统,从来不是靠“永不过期”这一个开关,而是靠可观察、可干预、带兜底的刷新链路。逻辑过期字段存哪、锁怎么设、失败后降级策略是什么,这些细节比“要不要异步”重要得多。











