缓存“永不过期”但后台异步更新,核心是应用层控制逻辑过期:缓存值内嵌expire_at时间戳,读时判断是否过期并异步刷新,写入用SET无TTL,配合Redis分布式锁防重复更新,并辅以定时预热兜底。

缓存“永不过期”但后台异步更新,核心不是靠 Redis 的 TTL 机制,而是把过期控制权交还给应用层——用“逻辑过期”代替“物理过期”。这样既避免了缓存瞬间失效带来的击穿压力,又保证数据能定期刷新,兼顾响应速度与最终一致性。
一、结构设计:缓存值里自带过期时间
不依赖 SETEX 或 EXPIRE,而是把数据和它的逻辑过期时间戳一起序列化存储。例如:
- 缓存 value 是一个 JSON 字符串:
{"data": {...}, "expire_at": 1757982000} - 或 PHP 数组:
['data' => $user, 'expire_at' => time() + 300] - 写入时统一用
SET(无过期),确保 key 永远存在
二、读取逻辑:先判断再决定是否刷新
每次读缓存都做两件事:解包数据 + 比对当前时间与 expire_at:
- 如果
time() → 直接返回data,不触发任何后端操作 - 如果已过期 → 启动异步任务刷新(如投递到消息队列、调用 swoole task、或用 pcntl_fork 子进程),同时立即返回旧的
data - 关键点:绝不阻塞用户请求,旧数据可接受,只要它“还能用”
三、刷新执行:单线程+原子更新防重复
异步刷新不是谁都能干,必须加一层协调机制:
- 尝试用 Redis 的
SET key val NX EX 30获取一个短期锁(比如 30 秒) - 只有拿到锁的进程才查库、组装新数据、重写缓存(含新的
expire_at) - 没抢到锁的跳过,下次读时再检查——天然实现“一人刷新,多人受益”
四、补充保障:定时预热兜底
纯靠读触发刷新会有冷启动问题,建议搭配 Cron 定时脚本:
- 每 5 分钟跑一次,扫描一批高频 key(如商品 ID、用户主页)
- 对每个 key 执行一次“强制刷新”流程(查 DB → 构造新 value →
SET写入) - 这样即使某 key 长时间没人访问,也不会一直陈旧











