redis本身不提供异步重建机制,所谓“异步重建”必须由应用层实现,否则所有方案都会在高并发下失效或引发脏写;因其单线程执行核心命令,无key过期自动回调或后台查库能力,依赖notify-keyspace-events易丢消息、多实例重复重建,真正可行路径仅有互斥锁+线程池、逻辑过期+守护线程、消息队列解耦三类。

Redis 本身不提供异步重建机制,所谓“异步重建”必须由应用层实现,否则所有方案都会在高并发下失效或引发脏写。
为什么不能依赖 Redis 自身触发异步加载
Redis 是单线程执行命令的(6.0+ 虽有 I/O 多线程,但 GET、SET、EXPIRE 等核心操作仍在主线程),它没有“key 过期后自动调用回调函数”或“后台线程查库回填”的能力。误以为 EXPIRE 或 Redisson 的监听能触发业务逻辑,会导致缓存击穿照旧发生。
常见错误现象:
- 监听
__keyevent@0__:expired频道,但消息可能丢失(Redis 默认不开启 notify-keyspace-events)或延迟严重 - 监听到 expired 后直接查 DB,多个实例同时收到同一条事件,重复重建
- 没做幂等校验,同一 key 被多次写入不同版本数据
应用层异步重建的三种可行路径
真正落地时,只有这三类方式经得起生产压测,且各自适用边界清晰:
-
互斥锁 + 线程池异步回填:适用于读多写少、允许短暂等待的场景。用
SETNX占位(如SET shop:1001_lock "1" NX EX 5),成功者提交CompletableFuture或ThreadPoolExecutor异步查库;失败者 sleep 后重试GET,不阻塞主线程 -
逻辑过期 + 守护线程预刷新:适用于可预测热点(如首页 Banner、活动榜单)。value 中嵌入
"expire_at": 1741603200字段,后台任务(如 Spring@Scheduled(fixedDelay = 30000))定期 SCANbanner:*扫描临近过期的 key,用SET显式更新并重设 TTL -
消息队列解耦重建:适用于强一致性要求不高、允许最终一致的场景。缓存未命中时发一条
{"key": "product:1001", "version": 12345}到 Kafka;消费者先比对当前缓存中的version字段,旧版本直接丢弃,避免乱序/重复导致脏写
容易被忽略的三个致命细节
这些点不处理,再“异步”的设计也会在凌晨三点崩给你看:
- 占位锁(如
shop:1001_lock)必须设EXTTL(推荐 3–5 秒),否则持有线程崩溃后锁永久残留,后续所有请求全部卡死 - 守护线程扫描不能用
KEYS *—— 生产环境禁用,改用SCAN+ 业务前缀 +OBJECT FREQ辅助判断热度,否则一次全量扫描就能打满 Redis CPU - MQ 消费端写缓存时,必须用
SET全量覆盖,禁止HINCRBY或LPUSH类增量指令,否则消息乱序会把库存从 100 写成 99 再写回 100
异步重建不是加个线程或扔条消息就完事,关键在“谁来判重、谁来兜底、谁来感知失败”。监控 cache_miss_rate 和 db_load_5m 的联动突增,比任何架构图都更能暴露你漏掉了哪一环。











