不能自动更新本地缓存,因Redis Pub/Sub仅传递无语义字符串消息,需手动解析type/key字段并调用invalidate()或put();推荐JSON消息含type、key、ts、v,用Lettuce异步订阅,优先invalidate,配合TTL与定时扫描兜底。

不能靠 PUBLISH / SUBSCRIBE 自动更新本地缓存。它只发字符串,不带语义、不保送达、不记历史——你得自己解析消息、自己调 invalidate() 或 put()。
为什么不能直接用 SUBSCRIBE 触发 Caffeine/Guava 刷新
Redis 的 Pub/Sub 是纯通道,不是事件总线。它不区分“删除”还是“更新”,不携带业务上下文,也不保证消息不丢。你收到一条 "user:123",但不知道该删、该刷、还是该忽略;更没法知道这条消息是不是重复发的。
-
JedisPubSub是阻塞式回调,必须扔进独立线程跑,否则主线程卡死 -
Lettuce的StatefulRedisPubSubConnection支持异步监听,还能复用 Netty 连接池,更适合生产环境 - 别用
PSUBSCRIBE监听通配符 channel,比如cache:*—— 一旦 channel 名膨胀,容易误触、抖动、甚至压垮连接数
消息体设计必须包含 type 和 key
只传 ID 或完整对象 JSON 都是典型翻车点。消息至少得有 type("DELETE" / "UPDATE")和 key("user:456"),否则本地缓存根本无法决策。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 避免只传原始数字
"456":不同服务对同一 ID 解释可能冲突(是用户 ID?订单 ID?) - 别序列化整个对象发过去:网络开销大,且本地缓存往往只需要 invalidate,不需要全量数据
- 必须加
ts(时间戳)或v(版本号):断线重连后收到旧消息,或网络重复投递,没判重逻辑就会反复刷新甚至写入脏数据 - 推荐格式:
{"type":"DELETE","key":"user:456","ts":1718234567,"v":"2"}
收到消息后该 invalidate 还是 refresh
选 invalidate(),别一收到就查 DB 再 put()。这不是偷懒,是防雪崩、防脏写、保延迟可控。
- 多个实例同时收到同一条
UPDATE消息,如果都去查 DB,瞬间并发打穿数据库 - DB 查询慢、超时或报错时,
put(null)或put(old_value)会污染本地缓存 -
invalidate()后,下一次请求自然触发加载,配合 Caffeine 的refreshAfterWrite或expireAfterAccess就够用 - 如果业务对“首次访问延迟”敏感,可搭配预热策略,而不是在通知里硬塞 DB 查询逻辑
多实例怎么确保都收到通知
每个服务实例起一个独立连接,都 SUBSCRIBE 到同一个 channel(比如 cache-invalidate),Redis 天然支持广播。但现实约束很硬:
- 连接断开期间的消息完全丢失:没 ack、没 offset、不支持断线重连后补推
- 线上建议搭配心跳 + 定期全量校验:比如每小时扫一次 Redis 热 key,比对本地缓存是否存在/是否过期
- 不要指望 Pub/Sub 做最终一致性兜底,它只是轻量级实时通知层;强一致场景必须走 Canal 或双写 + 补偿
真正难的不是写几行 PUBLISH,而是消息体怎么设计、断线怎么兜底、重复怎么判重、以及什么时候该放弃 Pub/Sub 改用更可靠的方案。这些细节不处理好,缓存不一致问题只会从“偶尔出现”变成“稳定复现”。

















