逻辑过期通过将过期时间嵌入value而非依赖redis物理过期,避免key消失导致的并发穿透;配合setifabsent互斥锁控制刷新、异步更新+兜底告警,以短暂不一致换取系统稳定性。

逻辑过期不是让 Redis 自己失效 key,而是把过期时间“塞进 value 里”,让业务代码自己判断——这样 key 永远不被 Redis 清掉,也就不会出现“瞬间全量穿透”。它本质是用短暂的数据不一致,换掉数据库雪崩风险。
为什么不能直接用 setex?
因为 setex 是物理过期:key 到点就从 Redis 里消失。一旦消失,所有并发请求同时发现“缓存没了”,全部涌向 DB。逻辑过期绕开了这个机制——set 写入,不设 TTL;过期判断交给业务层的 expireTime 字段。
-
setex适合冷数据或弱一致性场景,但对秒杀商品这类热点 key 就是定时炸弹 - 逻辑过期要求你封装一个带
expireTime和data的类(比如RedisData),序列化后存进 Redis - 读取时必须反序列化、解析
expireTime,再和当前时间比——别漏了时区或精度问题(推荐用Instant而非LocalDateTime)
如何避免多个线程同时刷新缓存?
即使用了逻辑过期,也不能放任所有线程都去查 DB。必须加一层轻量级互斥控制——不是锁住整个读流程,而是只锁“刷新动作”本身。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 用
setIfAbsent(即SETNX)尝试写一个临时锁 key,比如lock:shop:123 - 成功拿到锁的线程才触发异步刷新(比如用线程池提交一个
Runnable),然后立即返回旧数据 - 没拿到锁的线程跳过刷新,直接返回缓存里的旧值——不要 sleep 后重试,那会拖慢响应
- 锁 key 必须设 TTL(比如 5 秒),防止刷新线程崩溃导致死锁
逻辑过期的典型误用点
很多人以为“逻辑过期 = 不管缓存内容,永远返回旧值”,结果导致用户看到明显过时的价格或库存。关键在于:过期判断和刷新触发必须严格耦合。
- 不能只检查
expireTime是否小于现在时间就完事——得确认 DB 真实数据是否已变更(比如查下updated_at字段) - 异步刷新失败不能静默吞掉:要记录日志、告警,甚至降级为同步重建(否则缓存将长期 stale)
- 预热阶段必须写入合法的
expireTime(比如当前时间 + 10 分钟),而不是随便填个1970-01-01,否则一查就刷新,反而放大压力 - 如果业务允许最终一致性,刷新完成后建议用
set覆盖整个RedisData,而不是只更新data字段——避免序列化/反序列化不一致
逻辑过期真正难的不是代码怎么写,而是什么时候该刷新、刷新失败怎么兜底、旧数据能容忍多久——这些都要结合业务 SLA 来定,而不是套个模板就完事。










