大家好,我是程序员柏风,AI 最全的 Vibecoding 学习地图 http://coderoadmap.cn 作者。
发布订阅:跨实例的消息广播
这一课你将能做到
- 用两个终端完成 SUBSCRIBE/PUBLISH 全流程,读懂 PUBLISH 返回的「订阅者数量」意味着什么
- 说清 Pub/Sub「不持久、不补投、无 ACK」的三大限制,判断什么场景能用、什么场景必须上 MQ
- 用 Spring 的 RedisMessageListenerContainer 搭一个跨实例「缓存失效广播」骨架,对应本站 Caffeine 多实例不同步问题
为什么要学
第 6 课留了一个尾巴:多实例各自持有 Caffeine 本地缓存,A 实例更新数据清了自己的缓存,B 实例还拿着最长 30 秒旧数据。如果要求更快一致,就需要A 实例更新后立刻通知所有实例清缓存——广播。
Redis 的 Pub/Sub 就是现成的广播电台:发布者喊一嗓子,所有订阅该频道的实例同时收到。它是 Redis 里最轻的「消息」能力——但轻是有代价的:不落盘、离线不补。这一课教你用对它,也教你什么时候不能用。
先建立图像
┌─ 实例A(SUBSCRIBE coderoadmap:cache-evict)
PUBLISH ──────┼─ 实例B(SUBSCRIBE coderoadmap:cache-evict)
(fire) └─ 实例C(SUBSCRIBE coderoadmap:cache-evict)
频道 = 广播电台频率;订阅 = 开着收音机
特点:没人开机时喊了也白喊(消息即焚);收音机只收「开机后」的广播
跟着做
步骤 1:双终端体验收发
终端 1(订阅者):
docker exec -it redis-dev redis-cli -a devpass123
SUBSCRIBE demo:chat
# 进入长驻等待,收到消息会打印 message 频道 内容
终端 2(发布者):
docker exec redis-dev redis-cli -a devpass123
PUBLISH demo:chat "hello-subscribers"
# (integer) 1 ← 返回值:这个频道当前有 1 个订阅者收到了
切回终端 1:已实时收到。PUBLISH 返回值就是「送达数」——0 表示没人订阅,消息已丢且不会有任何补偿。
步骤 2:PSUBSCRIBE 通配订阅
终端 1 重新订阅:
PSUBSCRIBE coderoadmap:cache-evict:*
终端 2:
PUBLISH coderoadmap:cache-evict:tracks "coderoadmap:tracks:list"
PUBLISH coderoadmap:cache-evict:articles "coderoadmap:articles:list"
通配符让一个监听器接收一类频道的消息——按缓存分组广播,语义清晰。
步骤 3:亲手验证「离线即丢」
# 确保所有订阅终端都 UNSUBSCRIBE 后:
PUBLISH demo:chat "nobody-listening"
# (integer) 0 ← 0 个订阅者收到
# 现在再开一个新的订阅终端 → 永远收不到刚才那条
步骤 4:Spring 侧监听骨架
第 6 课的场景落到代码(骨架,理解三件套结构):
@Configuration
public class CacheEvictPubSubConfig {
@Bean
RedisMessageListenerContainer cacheEvictContainer(RedisConnectionFactory factory) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(factory);
container.addMessageListener((Message message, byte[] pattern) -> {
String body = new String(message.getBody(), StandardCharsets.UTF_8);
// body = 要失效的 key,如 "coderoadmap:tracks:list"
// 按前缀清对应 Caffeine cache:cacheManager.getCache("tracks").clear()
}, new PatternTopic("coderoadmap:cache-evict:*"));
return container;
}
}
三件套:RedisMessageListenerContainer(容器,管线程与断线重连)+ MessageListener(回调)+ ChannelTopic/PatternTopic(频道)。
步骤 5:发布侧一行代码
// 写路径清完自己的缓存后,广播给所有实例
stringRedisTemplate.convertAndSend(
"coderoadmap:cache-evict:tracks", "coderoadmap:tracks:list");
其他实例收到后清对应 Caffeine cache——30 秒不一致窗口被压缩到毫秒级。
讲清楚要点
- Pub/Sub 是即发即失(fire-and-forget):消息不在 Redis 存档,订阅者不在线就收不到;发布者只能从返回值(送达数)发现,无法补投。
- 它解决的是「通知」不是「传递」:通知丢了的兜底是各层 TTL(Caffeine 30s、Redis 5min)——广播失败最坏回到第 6 课的最终一致,不会出错数据。这就是它能用于缓存失效通知的根本原因。
- PUBLISH 返回订阅者数:生产可以借此打点「广播送达率」,发现订阅端掉线。
- Spring 侧三件套各司其职:容器管生命周期与重连(断线后自动重新订阅),监听器管业务回调,Topic 管匹配。
- 选型标尺:通知类、可丢失、低频 → Pub/Sub;订单类、不可丢、要堆积重试 → MQ(RabbitMQ/Kafka)或 Redis Stream(有持久化的流,支持消费确认)。
你可能会懵的地方
-
Pub/Sub 和 List 队列(第 4 课)区别? List 是点对点消费(一条消息被一个消费者拿走);Pub/Sub 是广播(每个订阅者都收到一份)。一个是排队,一个是喊话。
-
消息丢失到底多严重? 看场景:缓存失效通知丢了 → TTL 兜底,无感;支付结果通知丢了 → 业务事故。前者能用 Pub/Sub,后者换工具。
-
Redis 重启订阅关系还在吗? 订阅关系由客户端持有,断线重连后 Spring 容器会自动重新订阅;但断线期间的消息已经没了。
-
为什么广播 clear 而不是 evict 具体 key? 简单可靠:Caffeine 容量上限 200 条,clear 成本可忽略;精确失效收益小、复杂度高。
-
本站现在用了吗? 没有——30 秒 TTL 已满足需求。这是「架构演进」素材:先有 TTL 兜底,规模大了再加广播,工具尺寸匹配问题规模。
错法对照
| 错法 | 问题 | 正解 |
|---|---|---|
| 用 Pub/Sub 传订单/支付消息 | 离线即丢无补偿 | MQ 或 Redis Stream |
| 默认 PUBLISH 就算送达 | 返回 0 也算"发布成功" | 检查送达数 + TTL 兜底 |
| 频道名随意起 | 多业务互相串台 | coderoadmap:cache-evict:* 分层命名 |
| 广播里塞大 payload | 广播放大 N 倍流量 | 只传 key/事件名,收方自取 |
| 收到消息直接查库 | 订阅回调阻塞 | 快速清缓存/入队,重活异步做 |
卡点急救
- PUBLISH 返回 0:没订阅者在线。先起订阅端再测。
- 订阅终端卡住不动:SUBSCRIBE 是长驻阻塞模式,正常;Ctrl+C 退出。
- Spring 监听器没触发:确认容器 Bean 生效(Redis 开关开着)、频道通配一致、序列化方式与发布侧匹配(String body)。
- 收到的消息是字节数组乱码:按发布时的序列化方式解码,统一 UTF-8 String。
- 重连后想补历史消息:Pub/Sub 无历史,想要重放请用 Redis Stream。
面试常问
-
Redis Pub/Sub 的缺点? 不持久、离线不补、无 ACK。适合通知类可丢消息;重要消息用 MQ 或 Redis Stream。
-
怎么实现多实例本地缓存同步失效? 更新实例清自身缓存后 PUBLISH 失效事件,各实例的 RedisMessageListenerContainer 收到后 clear 对应 Caffeine cache;TTL 兜底保证广播丢失也不出脏数据。
-
Pub/Sub、List、Stream 选型? 广播可丢 → Pub/Sub;点对点轻量任务 → List/BRPOP;点对点要持久/ACK/重放 → Stream 或专业 MQ。
自测三问
- PUBLISH 返回 (integer) 0 意味着什么?消息去哪了?
- 本站缓存失效广播里,广播丢失的最坏后果是什么?靠什么兜底?
- RedisMessageListenerContainer 三件套是哪三个角色?各自负责什么?
小结一句话
Pub/Sub 是「即发即失」的跨实例广播:适合缓存失效通知这类丢了也有 TTL 兜底的事件,不可丢的消息请上 MQ 或 Stream。
延伸
- Redis Pub/Sub 官方文档
- 下一课:监控与测试——INFO 看健康、SLOWLOG 抓慢命令、Testcontainers 写真 Redis 的测试。