redis pub/sub 无法自动同步本地缓存,需手动解析消息并调用 invalidate()/put();推荐消息结构含 type、key、ts、v 字段,使用 lettuce 异步订阅,优先 invalidate 而非主动刷新,并依赖 ttl 和定时扫描兜底。

Redis Pub/Sub 不能自动更新任何本地缓存,它只负责发字符串消息;要同步 Caffeine 或其他本地缓存,必须手动解析消息并调用 invalidate() 或 put()。
Pub/Sub 消息体设计必须带 type 和 key 字段
只发一个 ID(如 "123")或一整段 JSON 对象,是线上最常踩的坑。前者无法区分业务域("123" 是用户 ID 还是订单 ID?),后者徒增序列化开销且本地缓存往往不需要全量数据。
推荐结构固定为:
{"type":"DELETE","key":"order:456789","ts":1718234567,"v":"1"}
-
type必须明确:只支持"DELETE"、"UPDATE",避免收到消息后猜操作意图 -
key必须带业务前缀,和 Caffeine 中实际使用的 cache key 完全一致(比如 Caffeine 里存的是"order:456789",这里就不能只写"456789") -
ts时间戳 +v版本号,是做幂等去重的基础;没这两个字段,网络抖动重发就会导致缓存被反复清空
Lettuce 是比 Jedis 更适合做订阅客户端的选择
Jedis 的 JedisPubSub 是阻塞式回调,一旦丢连接或网络卡顿,整个线程就挂住——你不能把它扔进 Web 请求线程池里跑。而 Lettuce 基于 Netty,StatefulRedisPubSubConnection 支持异步监听、自动重连、连接复用。
关键配置点:
- 订阅必须在独立线程启动,不要和业务线程共用连接池
- 别用
PSUBSCRIBE监听通配符(如"cache:*"),channel 名要收敛,例如统一用"cache:order"、"cache:user" - 务必设置
setAutoReconnect(true)和合理的timeout,否则断网 30 秒,这期间所有变更通知就彻底丢失
收到消息后优先调用 invalidate(),而不是立刻查 DB 再 put()
看似“主动刷新”更及时,实则埋下雪崩和脏写隐患:
- 一条
PUBLISH可能被 10 个服务实例同时收到,如果都去查 DB,瞬间并发翻 10 倍 - DB 查询超时或返回 null,
put(null)就污染了本地缓存,后续请求全挂掉 - Caffeine 本身有
refreshAfterWrite,下一次请求自然触发加载,延迟可控且安全
所以标准动作链就是:SUBSCRIBE → parse JSON → if type == "DELETE" → caffeine.invalidate(key),仅此而已。
断线期间的消息完全不可恢复,得靠兜底机制补漏
Redis Pub/Sub 没 offset、不存历史、不保证送达——这是它的定位决定的,不是 bug。这意味着只要订阅连接中断超过 1 秒,那段时间的所有变更就永远丢失。
生产环境必须加两层兜底:
- 对核心热点数据(如商品价格、库存),保留短 TTL(比如 60 秒)+ 主动刷新逻辑,哪怕 Pub/Sub 断了,最多 stale 60 秒
- 定期(如每 5 分钟)用
SCAN扫描 Redis 中的热点 key,比对本地 Caffeine 的 lastAccessTime,偏差过大就强制 invalidate
真正的难点不在怎么发消息,而在怎么让“消息丢了也不翻车”。Pub/Sub 只是快车道,但你得自己修应急便道。











