Redis二级缓存回源保护机制的核心是防止缓存击穿,通过互斥锁(SETNX)与逻辑过期字段结合,在本地缓存失效且Redis未命中时实现请求串行化、兜底返回和降级处理,并引入本地冷却窗口与分层key设计保障兼容性与稳定性。

Redis二级缓存回源保护机制的核心,是避免单个热点 key 失效瞬间引发的并发重建——也就是缓存击穿。它不靠“不设过期时间”这种粗暴方式,而是在一级本地缓存(如 Caffeine)和二级 Redis 缓存之间,加一层协调逻辑,让回源请求串行化、有兜底、可降级。
为什么不能只靠本地缓存或只靠Redis?
本地缓存(如 Caffeine)延迟低但无法共享,多实例下各自失效会导致重复回源;纯 Redis 缓存能共享但无本地兜底,一旦 key 过期,所有节点同时打 DB。二级缓存不是简单叠加,而是要明确分工:
- 本地缓存负责快速响应、抗抖动,TTL 略短(如 10 秒),允许短暂不一致
- Redis 缓存负责跨节点一致性,TTL 较长(如 10 分钟),但需防击穿
- 回源保护逻辑必须落在“本地缓存未命中 → 查 Redis → Redis 也未命中”这个链路中
用互斥锁 + 逻辑过期组合实现回源保护
单独用 SETNX 做分布式锁容易卡住请求,单独用逻辑过期又可能返回陈旧数据。二者结合才是生产可用方案:Redis 中存的是带 expire_time 字段的结构体(如 JSON),本地缓存只存原始值。读取流程如下:
- 先查本地缓存,命中则直接返回
- 未命中则查 Redis,若 Redis 返回空或结构体中
expire_time已过期,进入保护逻辑 - 用
SET lock:product:123 "1" NX EX 30尝试抢锁;抢到则异步查 DB 并更新 Redis(含新expire_time),再刷新本地缓存;没抢到则等待最多 200ms 后重试读本地缓存 - 无论是否抢锁成功,当前请求都返回本地缓存旧值(若有)或 Redis 中的过期值(兜底),绝不阻塞
关键点:NX EX 防死锁,expire_time 字段控制业务语义过期,本地缓存不设强一致性,只做性能缓冲。
本地缓存失效后如何避免反复触发Redis查询?
常见错误是本地缓存一过期就立刻去 Redis 查,结果大量线程在 Redis 层又撞上同一个空 key。正确做法是引入“回源冷却窗口”:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 当本地缓存失效,且 Redis 中该
key也不存在时,写入一个短期的本地占位标记(如loading:product:123,TTL=500ms) - 后续请求在冷却窗口内直接返回 null 或默认值,不查 Redis,也不抢锁
- 冷却结束后才允许再次尝试完整回源流程
这能有效抑制因网络抖动、GC 暂停等导致的瞬时本地缓存集体失效带来的 Redis 查询风暴。注意:这个冷却标记必须是线程安全的本地变量或 ConcurrentHashMap,不能依赖 Redis。
逻辑过期字段怎么序列化才不影响兼容性?
往 Redis 存 JSON 时硬塞一个 expire_time 字段,会破坏原有数据结构。更稳妥的方式是用分层 key:
- 主数据 key:
product:123,只存业务数据(如{"name":"iPhone","price":6999}) - 元数据 key:
meta:product:123,存{"expire_time":1757032080,"version":2} - 读取时两个 key 一起查,任一缺失或元数据过期,即触发回源
这样升级时只需调整读逻辑,老数据无需迁移;下游服务如果只认 product:123,完全无感知。别把逻辑过期塞进业务数据体里——那是耦合的开始。
真正难的不是加锁或设过期时间,而是判断什么时候该让请求“等等”,什么时候该让它“拿旧的用”,以及怎么让本地和远程两层缓存彼此不猜忌。回源保护不是越严越好,而是要在一致性、延迟、资源消耗之间找那个动态平衡点。

















