一文搞懂 Redis Pub/Sub:把它想成一座城市广播站
文章目录
-
- 一、先把城市广播站和 Redis 对上号
- 二、一条广播到底怎么发出去
- 三、Pub/Sub 最大的边界:广播过去就过去了
- 四、模式订阅:把自动搜台打开
- 五、Channel 不是 Key,数据库编号也隔离不了它
- 六、订阅连接为什么像进入了“收听模式”
- 七、Redis Cluster:全城广播为什么会越来越吵
- 八、Sharded Pub/Sub:把全城广播改成分区广播
- 九、慢听众会怎样拖累广播站
- 十、线上怎么判断广播站还正常
- 十一、完整 redis-cli 实验
- 十二、一个更真实的用法:配置刷新广播
- 十三、Pub/Sub、List、Stream 和专业 MQ 怎么选
- 十四、线上故障怎么排查
- 十五、八个高频误区
- 十六、上线前检查清单
- 十七、总结:广播负责快,台账负责可靠
- 参考资料
晚上十一点,城市广播站突然收到一条通知:跨江大桥临时封闭。
主持人拿起话筒,在“城市交通”频道里播报:
前方桥梁检修,请车辆绕行滨江路。
正在收听这个频道的出租车、公交调度室和居民收音机,几乎同时听到了消息。广播站不用知道听众是谁,也不用挨个给他们打电话;听众同样不需要认识主持人,只要调到正确频率即可。
这就是发布/订阅模式,也是 Redis Pub/Sub 最适合解决的问题:让消息生产者和在线消费者通过频道解耦,并把一条消息实时推送给多个订阅者。
不过,广播也有一个致命特点:收音机没开机,刚才那段路况就错过了。节目不会因为你迟到而自动倒带,更没有“签收回执”帮你确认每位听众都听懂了。
所以 Redis Pub/Sub 用起来很轻,边界也必须看得很清。它适合在线通知,却不是一个默认可靠的消息队列。

图 1:广播一开口,在线听众立即收到;离线听众不会得到历史补播。
本文以 Redis 8.6.1 的实际实验为背景。命令和公开语义以 Redis 官方文档为准,Cluster 内部传播方式与默认配置可能随版本变化。
一、先把城市广播站和 Redis 对上号
先看一张角色表:
| 城市广播站 | Redis Pub/Sub | 作用 |
|---|---|---|
| 主持人 | Publisher | 通过 PUBLISH 发布消息 |
| 广播频率 | Channel | 对消息进行分类 |
| 收音机 | Subscriber | 通过 SUBSCRIBE 接收频道消息 |
| 自动搜台 | Pattern Subscription | 用 PSUBSCRIBE 匹配一批频道 |
| 实时播报 | Message | Redis 推送给在线订阅者的载荷 |
| 分区广播 | Sharded Pub/Sub | 只在频道所属 Cluster 分片内传播 |

图 2:Publisher 不认识 Subscriber,双方只约定 Channel 名称。
这里最重要的不是命令,而是两个“互相不知道”:
- 发布者不知道有多少订阅者,也不需要维护订阅者地址;
- 订阅者不知道消息由哪个发布者产生,只关心自己订阅的频道。
这种解耦让临时通知非常方便。比如配置刷新、聊天室在线消息、服务状态广播、实时比分和运维告警,都可以先想到 Pub/Sub。
但它只解耦了发送关系,并没有自动提供持久化、确认、重试、消费进度和死信队列。
二、一条广播到底怎么发出去
准备两个终端,先让第一个终端订阅交通频道:
redis-cli -p 6403 SUBSCRIBE city:traffic
Redis 会先推送一条订阅确认:
1) "subscribe"
2) "city:traffic"
3) (integer) 1
三个字段可以理解为:
- 事件类型是
subscribe; - 成功订阅的频道是
city:traffic; - 当前连接一共订阅了一个频道或模式。
然后在第二个终端发布路况:
redis-cli -p 6403 PUBLISH city:traffic "bridge closed"
发布端得到:
(integer) 1
订阅端得到:
1) "message"
2) "city:traffic"
3) "bridge closed"
这次事件的三个字段分别是 message、原始频道名和消息内容。
普通模式下,一条消息的路径非常短:
Publisher
│ PUBLISH city:traffic "bridge closed"
▼
Redis 查找频道订阅关系
├── 推送给 Subscriber A
├── 推送给 Subscriber B
└── 推送给 Subscriber C
Redis 不会先把消息写进某个 Key,也不会生成类似 Stream ID 的持久化编号。它只是找到此刻在线的订阅连接,把消息写进这些连接的输出缓冲区。
2.1 PUBLISH 返回 1,代表业务成功了吗
不代表。
返回值表示 Redis 把消息发送给了多少个符合条件的客户端连接。它不能证明:
- 客户端业务回调已经执行;
- JSON 解析成功;
- 数据库更新完成;
- 下游服务没有崩溃;
- 用户真的看到了通知。
这就像广播站知道“有一台收音机正在收听”,却不知道听众是否听懂、是否照做。
如果业务需要处理确认,必须由应用自己增加回执频道,或者直接选用 Redis Stream、RabbitMQ、Kafka 等带确认与进度管理的方案。
2.2 取消订阅
协议层可以使用:
UNSUBSCRIBE city:traffic
完全退订后,订阅计数会变为 0。不过 redis-cli 进入交互式订阅模式后,不接受普通键盘命令,通常用 Ctrl+C 退出。客户端 SDK 一般会封装独立的取消订阅 API,不应照搬命令行交互方式。
三、Pub/Sub 最大的边界:广播过去就过去了
假设某位司机在主持人播报时关闭了收音机:
# 没有任何订阅者在线
redis-cli -p 6403 PUBLISH city:traffic "bridge closed"
返回值是:
(integer) 0
十秒后司机重新订阅:
redis-cli -p 6403 SUBSCRIBE city:traffic
他不会收到刚才的 bridge closed。Redis 没有可以回放的历史记录,也不存在“从上次位置继续消费”。

图 3:Pub/Sub 是实时广播,不是带历史记录和签收回执的快递系统。
Redis 官方把这种语义称为 at-most-once,至多一次:消息可能到达一次,也可能因为网络断开、进程崩溃或处理错误而永久丢失,但 Redis 不会替你重发。
3.1 没有积压区
Pub/Sub 没有下面这些结构:
- 消息日志;
- 消费位点;
- Pending 列表;
- ACK;
- 自动重试;
- 死信队列。
订阅者处理不过来时,消息暂时堆在连接输出缓冲区里,而不是变成可恢复的队列积压。一旦连接因缓冲区超限被关闭,未读消息就丢了。
3.2 断线重连不等于恢复消费
客户端库通常能自动重连并重新执行 SUBSCRIBE,但它只能重新接收未来消息。断线窗口内发生的消息仍然无法补回来。
因此,订单创建、支付成功、库存扣减、账务记账等“少一条都不行”的事件,不应该只靠 Pub/Sub 传递。
3.3 有序也不是业务完成有序
同一订阅连接会按照 Redis 发布顺序接收消息,但应用拿到消息后可能交给线程池并行执行。先收到的任务完全可能后完成。
如果业务要求严格顺序,还需要单线程消费、按业务 Key 分区、序号校验或可持久化的顺序消息系统。
四、模式订阅:把自动搜台打开
城市里不只一个频道:
city:traffic
city:weather
city:emergency
district:east:traffic
如果监控中心想接收所有 city: 开头的广播,可以使用模式订阅:
redis-cli -p 6403 PSUBSCRIBE 'city:*'
发布:
redis-cli -p 6403 PUBLISH city:weather "heavy rain"
模式订阅收到的是四段消息:
1) "pmessage"
2) "city:*"
3) "city:weather"
4) "heavy rain"
它们依次表示消息类型、匹配模式、实际频道和消息内容。
Redis 使用 glob 风格匹配,常见规则包括:
*:匹配任意长度字符;?:匹配一个字符;[ab]:匹配集合中的一个字符。
例如:
PSUBSCRIBE 'city:*' 'district:?:alert' 'news:[a-c]'
4.1 一个消息为什么可能收到两遍
某个客户端既订阅了精确频道:
SUBSCRIBE city:traffic
又订阅了能够匹配它的模式:
PSUBSCRIBE city:*
此时发布一次:
PUBLISH city:traffic "bridge closed"
同一个连接会收到两份事件:一份类型是 message,另一份是 pmessage。

图 4:收音机既保存了固定频率,又打开自动搜台,同一段节目可能命中两条订阅规则。
如果客户端订阅了多个都能命中的模式,还可能收到更多份。Redis 不会替业务去重,因为这些订阅关系在协议层确实是不同的匹配结果。
所以,使用模式订阅时要么保证规则互斥,要么在消息中携带事件 ID,并由应用进行幂等处理。
4.2 PUBSUB NUMPAT 统计的是什么
它统计的是当前节点上的模式订阅数量,不是“有多少个频道被模式命中”,也不是一个全局业务消费者数量。
redis-cli -p 6403 PUBSUB NUMPAT
如果一个客户端订阅三个模式,计数会增加三,而不是一。
五、Channel 不是 Key,数据库编号也隔离不了它
很多人看到 city:traffic 的冒号命名,会误以为它是一个 Redis Key。
其实 Channel 和 Key Space 没有关系:
redis-cli -n 1 SUBSCRIBE city:traffic
redis-cli -n 10 PUBLISH city:traffic "hello"
即使订阅端选择数据库 1,发布端选择数据库 10,消息仍然可以送达。SELECT 不能给 Pub/Sub 频道做环境隔离。
频道也不会出现在下面这些命令中:
SCAN 0
KEYS '*'
TYPE city:traffic
DBSIZE
5.1 正确的频道命名方式
建议把环境、系统、业务和事件写进频道:
prod:mall:order:created
staging:mall:order:created
prod:config:payment:refresh
prod:chat:room:9527
常见格式是:
环境:系统:领域:事件
这样可以避免测试环境误订阅生产广播,也方便 ACL 使用频道模式约束访问范围。
不要把用户输入不加限制地拼成频道名,否则频道数量可能快速增长,权限和监控也会变得混乱。
六、订阅连接为什么像进入了“收听模式”
使用 RESP2 协议时,客户端一旦执行 SUBSCRIBE,连接就进入订阅状态。这个连接只能执行订阅相关命令、PING、RESET、QUIT 等少量命令,不能再像普通连接一样随意 GET、SET。
原因很好理解:服务器正在不断向这条连接主动推送消息,普通请求响应与推送事件如果没有更强的协议区分,客户端很难安全复用。
6.1 为什么客户端要准备专用连接
生产代码通常会为 Pub/Sub 使用独立长连接:
业务连接池:GET / SET / HGET / EVAL ...
订阅连接:SUBSCRIBE / PSUBSCRIBE,持续读取推送
不要从普通命令连接池里借出一条连接订阅后不归还,否则可能耗尽连接池,或者把已经进入订阅状态的连接交给其他请求。
6.2 RESP3 有什么变化
RESP3 能更明确地区分普通响应和服务器推送。在 RESP3 的订阅状态下,客户端可以继续发送其他命令。
但“协议允许”不代表“工程上应该把所有工作塞进一条连接”。专用订阅连接更容易做重连、流量隔离、延迟监控和故障定位,因此仍然是更稳妥的默认方案。
6.3 广播频道也需要权限边界
Pub/Sub 不写 Key,不代表它应该绕过权限控制。Redis ACL 可以同时限制命令和可访问频道。例如配置订阅者只需要读取 prod:config:*,就不应该拥有任意 PUBLISH 权限,更不应该订阅聊天、订单等无关频道。
可以为不同角色分别设计权限:
配置发布者:允许 PUBLISH,只能访问 &prod:config:*
配置订阅者:允许 SUBSCRIBE/PSUBSCRIBE,只能访问 &prod:config:*
运维观察者:允许 PUBSUB、CLIENT LIST 等只读诊断命令
权限变更前应使用测试账号验证实际命令和频道模式,避免一条过宽的 &* 让所有服务都能收听全城广播。密码和 ACL 规则也不应写进文章、日志或普通配置仓库。
七、Redis Cluster:全城广播为什么会越来越吵
单机 Redis 只有一个广播站,查找订阅者很直观。进入 Redis Cluster 后,订阅者可能连接不同节点。
传统全局 Pub/Sub 允许客户端连接任意节点订阅,也允许发布者向任意节点发布。Cluster 会把消息传播到其他节点,让远端节点上的订阅者也能收到。
这很方便,但意味着广播流量会经过 Cluster Bus。分片数量和消息量不断增长时,全局广播会消耗越来越多的节点间带宽。
7.1 一个容易误判的返回值
本次三节点实验中:
- Subscriber 连接
7410; - Publisher 在
7412执行PUBLISH city:global all-city-message; PUBLISH返回0;- 但
7410上的 Subscriber 实际收到了消息。
这不是矛盾。Redis 官方 PUBLISH 文档明确说明:**Cluster 模式下,返回值只统计与发布者连接在同一节点上的客户端。**远端节点收到并转发的订阅者不计入这个数字。
所以不能在 Cluster 中用 PUBLISH 返回 0 直接断言“全系统无人订阅”。
八、Sharded Pub/Sub:把全城广播改成分区广播
Redis 7.0 引入 Sharded Pub/Sub。它把频道按照和 Key 相同的算法映射到 16384 个 Hash Slot,再让消息只在对应分片内部传播。
对应命令是:
SSUBSCRIBE 订阅分片频道
SUNSUBSCRIBE 取消分片订阅
SPUBLISH 向分片频道发布
例如:
redis-cli -c -p 7410 SSUBSCRIBE 'city:{east}:traffic'
redis-cli -c -p 7411 SPUBLISH 'city:{east}:traffic' 'road closed'
频道 city:{east}:traffic 会根据 Hash Tag {east} 计算 Slot。Cluster 客户端把 SPUBLISH 路由到负责该 Slot 的 Master,消息再传播到这个分片的 Master 和 Replica,而不是广播给所有分片。

图 5:全局 Pub/Sub 像全城广播,Sharded Pub/Sub 像只通知东城区的社区广播。
8.1 为什么分片广播更容易横向扩展
假设有十个分片,某条消息只服务东区用户:
- 全局 Pub/Sub 仍要让整个集群知道;
- Sharded Pub/Sub 只在东区频道所属分片传播。
增加分片后,分片频道能够把不同业务或租户的广播流量摊开,Cluster Bus 不必为每条消息做全局扩散。
8.2 怎么查看分片频道
PUBSUB SHARDCHANNELS 'city:*'
PUBSUB SHARDNUMSUB 'city:{east}:traffic'
需要注意,这类统计反映目标节点或分片看到的订阅情况,不会自动把整个集群汇总成一个业务答案。排查时应该先计算频道 Slot、定位所属分片,再在相关节点检查。
8.3 全局和分片命令不能混着用
SUBSCRIBE 与 PUBLISH 是一组,SSUBSCRIBE 与 SPUBLISH 是另一组。订阅 SSUBSCRIBE city:{east}:traffic 后,用普通 PUBLISH 发布并不会变成分片消息。
客户端封装时应把两种频道类型区分清楚,避免发布端和订阅端使用了不同命令族,双方都以为对方掉线。
九、慢听众会怎样拖累广播站
订阅者可能在线,却因为 CPU 满载、事件循环卡住、网络很慢而来不及读取消息。Redis 只能先把待发送数据放入该客户端的输出缓冲区。
如果发布速度长期高于读取速度,缓冲区会越来越大:
Publisher 10000 条/秒
│
▼
Redis 输出缓冲区不断增长
│
▼
Subscriber 只能处理 1000 条/秒
Redis 不会把这当成可重放的消息积压。达到客户端输出缓冲区限制后,连接会被关闭,未读取消息随之丢失。
官方当前文档给出的 Pub/Sub 客户端默认限制包括:
- 硬限制 32 MB:到达后尽快关闭连接;
- 软限制 8 MB、持续 60 秒:超时后关闭连接。
具体生产值应以当前实例的配置为准:
redis-cli CONFIG GET client-output-buffer-limit
9.1 一条大消息为什么会被订阅者数量放大
假设一条消息大小为 1 MB,有 500 个在线订阅者。仅从网络复制角度看,Redis 可能需要向客户端发送约 500 MB 数据,还不包括协议和缓冲区开销。
因此不要把大文件、完整图片、超大 JSON 或批量报表直接塞进 Pub/Sub。更合理的做法是把对象放进对象存储或数据库,广播中只携带对象 ID、版本号和下载地址。
9.2 模式订阅也有匹配成本
PUBLISH 除了处理精确频道订阅者,还要检查已存在的订阅模式。模式数量极多时,每次发布都要承担额外匹配成本。
不要给每个用户动态创建多个宽泛模式。用户级路由可以考虑分层频道、网关内部路由或专业消息系统。
十、线上怎么判断广播站还正常
先看频道和订阅数量:
# 当前节点存在的普通频道
redis-cli PUBSUB CHANNELS 'prod:*'
# 指定频道的精确订阅连接数
redis-cli PUBSUB NUMSUB prod:config:refresh prod:chat:global
# 当前节点模式订阅总数
redis-cli PUBSUB NUMPAT
# 分片频道
redis-cli PUBSUB SHARDCHANNELS 'prod:*'
redis-cli PUBSUB SHARDNUMSUB 'prod:{east}:notice'
再看订阅客户端:
redis-cli CLIENT LIST TYPE pubsub
重点关注:
age:连接存活时间;idle:空闲时间;sub、psub、ssub:订阅数量;omem:输出缓冲区占用;- 客户端名称、地址和最后执行命令。
应用连接最好主动设置名称:
CLIENT SETNAME config-center-subscriber-01
这样报警时能快速定位是哪一个服务没有及时读取。
实例级指标可以查看:
redis-cli INFO stats | grep pubsub
常见字段包括:
pubsub_channels
pubsub_patterns
pubsubshard_channels
但这些指标只能证明“存在订阅关系”,不能证明业务回调健康。生产系统还应监控:
- 应用最后一次收到消息的时间;
- 订阅连接重连次数;
- 消息处理成功和失败数量;
- 端到端延迟;
- 输出缓冲区与网络带宽;
- 必须一致的数据是否通过其他渠道完成校验。
十一、完整 redis-cli 实验
下面用 Redis 8.6.1 演示关键语义。实验应使用独立实例,不要直接修改生产 Redis。
11.1 普通订阅和模式订阅
终端 A:
redis-cli -p 6403 --raw SUBSCRIBE city:traffic
终端 B:
redis-cli -p 6403 --raw PSUBSCRIBE 'city:*'
终端 C:
redis-cli -p 6403 PUBLISH city:traffic 'traffic-001'
返回 2,两条订阅连接都收到消息。
统计:
redis-cli -p 6403 PUBSUB NUMSUB city:traffic
redis-cli -p 6403 PUBSUB NUMPAT
本次输出为:
city:traffic
1
1
11.2 验证离线消息无法恢复
先关闭所有订阅者,再发布:
redis-cli -p 6403 PUBLISH city:offline 'missed-while-offline'
返回:
0
重新订阅后,再发布一条新消息:
redis-cli -p 6403 SUBSCRIBE city:offline
redis-cli -p 6403 PUBLISH city:offline 'after-reconnect'
订阅者只会看到 after-reconnect,不会看到 missed-while-offline。
11.3 验证同一连接重复收到
使用 RESP3,让同一连接先后订阅精确频道和模式:
SUBSCRIBE city:traffic
PSUBSCRIBE city:*
再发布:
redis-cli -p 6403 PUBLISH city:traffic 'duplicate-on-purpose'
实验日志中 duplicate-on-purpose 出现两次,分别来自直接匹配和模式匹配。
11.4 验证 Cluster 分片广播
在三主节点集群中计算分片频道 Slot:
redis-cli -p 7410 CLUSTER KEYSLOT 'city:{east}:traffic'
本次得到:
10965
订阅并发布:
redis-cli -c -p 7410 SSUBSCRIBE 'city:{east}:traffic'
redis-cli -c -p 7411 SPUBLISH 'city:{east}:traffic' 'district-message'
SPUBLISH 返回 1,订阅端收到 smessage。
11.5 验证全局消息与返回值差异
在 7410 订阅:
redis-cli -p 7410 SUBSCRIBE city:global
在 7412 发布:
redis-cli -p 7412 PUBLISH city:global all-city-message
发布端返回 0,但 7410 的订阅端确实收到消息。这证明 Cluster 会传播全局消息,同时也证明返回值只统计发布节点本地订阅连接。
完整可重复脚本会在文末本地备份中保留,脚本结束后关闭所有临时实例并检查端口释放。
十二、一个更真实的用法:配置刷新广播
假设支付服务有 30 个实例,配置中心修改了风控阈值。最简单的方案是向频道广播配置版本:
{
"eventId": "cfg-20260812-001",
"service": "payment",
"version": 42,
"changedAt": "2026-08-12T10:30:00+08:00"
}
发布频道:
prod:config:payment:refresh
每个在线实例收到消息后,根据 version 拉取最新配置。
这个设计比直接把完整配置塞进消息更稳:
- 消息小,网络放大较轻;
- 即使重复收到,也可以比较版本后忽略;
- 即使某实例断线错过广播,也能通过定时校验版本自愈;
- 配置真实来源仍在数据库或配置中心,不依赖 Pub/Sub 保存。
12.1 给“不可靠广播”补一条安全绳
推荐采用“广播加定时校验”:
配置变更
├── Pub/Sub 立即通知:追求低延迟
└── 实例定时拉取版本:弥补断线窗口
Pub/Sub 负责快,权威存储和定时校验负责最终正确。
12.2 处理函数仍要幂等
虽然 Pub/Sub 是至多一次语义,但模式重叠、应用重订阅逻辑和上游重复发布仍可能让业务看到重复事件。
配置刷新可以使用版本号去重;缓存失效可以重复执行;不可重复的副作用则需要事件 ID、状态表或其他幂等机制。
12.3 订阅成功之前,不要宣布服务已经就绪
应用进程启动并不等于订阅已经生效。下面这个时间窗口经常被忽略:
进程启动 → 建立 Redis 连接 → 发送 SUBSCRIBE → 收到订阅确认 → 真正可以接收
如果健康检查在建立连接前就返回成功,上游可能立刻发布第一条消息,而新实例尚未收到 subscribe 确认,结果就是启动阶段丢消息。
更稳妥的做法是:订阅协程收到目标频道的确认事件后,再把应用的 readiness 标记为就绪。连接断开时则立刻取消就绪状态,避免负载均衡器继续把依赖实时通知的请求交给失聪实例。
12.4 订阅回调不要直接干重活
接收循环最重要的任务是尽快从 Socket 取走消息。如果回调里直接查询数据库、调用外部 HTTP、压缩文件或等待锁,读取速度会被最慢的业务操作拖住,输出缓冲区也可能持续增长。
常见结构是:
专用订阅连接
│ 快速解析、校验
▼
有界内部队列
│
├── Worker 1
├── Worker 2
└── Worker 3
这里必须使用有界队列,并提前决定队列满了怎么办:丢弃可替代通知、合并同类刷新、降级成定时拉取,或者触发告警。无界队列只是把 Redis 输出缓冲区风险搬进应用内存。
12.5 优雅关闭也要分顺序
服务停止时,可以先从负载均衡器摘除,再停止接收新业务,取消订阅并关闭订阅连接,最后等待已经进入内部队列的任务在限定时间内完成。
但是不要把“优雅关闭”误认为可靠消费:进程被强杀、机器断电和网络故障仍然会留下无法恢复的消息窗口。真正不能丢的事件依然需要 Stream 或专业消息系统。
十三、Pub/Sub、List、Stream 和专业 MQ 怎么选
| 能力 | Pub/Sub | List | Stream | 专业消息队列 |
|---|---|---|---|---|
| 消息持久记录 | 否 | 元素仍在 List 时存在 | 是 | 通常支持 |
| 多个消费者都收到同一消息 | 天然支持 | 需要复制队列 | 可用多个消费组 | 通常支持 Topic/订阅 |
| 离线后补消费 | 否 | 可以 | 可以 | 可以 |
| ACK/Pending | 否 | 需要自行设计 | 支持 | 通常支持 |
| 消费组 | 否 | 简单竞争消费 | 支持 | 通常支持 |
| 实时性和使用成本 | 很轻 | 很轻 | 中等 | 更完整也更复杂 |
| 典型场景 | 在线通知、刷新广播 | 简单任务队列 | 可追踪消息流 | 核心业务事件、大规模消息平台 |
可以用一句话记忆:
丢了也能靠下一次状态同步纠正,用 Pub/Sub;必须知道谁处理过、失败后还要重试,用 Stream 或专业消息队列。
13.1 Pub/Sub 适合的场景
- 在线聊天室的瞬时提示;
- 配置或缓存刷新通知;
- 服务状态和运维广播;
- 实时仪表盘更新;
- 可以容忍偶发丢失的在线事件。
13.2 不应该只用 Pub/Sub 的场景
- 订单创建;
- 支付结果;
- 库存扣减;
- 用户权益发放;
- 审计日志;
- 必须重试、回放和追责的任务。
如果你想了解 Redis 自带的可持久化消息结构,可以继续阅读:一文搞懂 Redis Stream:把它想成一座有签收回执的快递分拣中心。
十四、线上故障怎么排查

图 6:先判断“没人收听、听众掉线、听众太慢,还是根本选错了工具”。
14.1 发布返回 0
依次检查:
- Subscriber 是否已经完成订阅确认;
- 频道名称、大小写和环境前缀是否一致;
- 发布端使用的是
PUBLISH还是SPUBLISH; - Cluster 中是否只是远端订阅者没有计入返回值;
- 客户端是否正在重连窗口;
- ACL 是否允许访问该频道。
14.2 有订阅连接,但业务没有反应
检查:
- 客户端事件循环是否被阻塞;
- 回调是否异常退出;
- 消息格式或版本是否兼容;
- 订阅线程是否把工作丢进已经满了的线程池;
CLIENT LIST TYPE pubsub中omem是否持续增大;- 日志中是否出现输出缓冲区超限导致的断开。
14.3 只有部分实例收不到
检查实例连接的 Redis 节点、订阅命令族、频道 Hash Tag、部署环境和客户端重连逻辑。Sharded Pub/Sub 下还要确认 Subscriber 是否连接频道所属分片的 Master 或 Replica。
14.4 Redis 网络突然暴涨
重点检查:
- 单条消息是否变大;
- 订阅者数量是否暴增;
- 是否误用全局 Pub/Sub 代替 Sharded Pub/Sub;
- 是否创建了大量重叠模式;
- 某次发布是否进入循环风暴;
- Subscriber 是否把收到的消息再次无条件发布回原频道。
Big Key 与 Hot Key 的排查方法可以参考:一文搞懂 Redis Big Key 与 Hot Key:把它想成仓储超市里的巨型货箱和爆款柜台。
十五、八个高频误区
误区一:Pub/Sub 就是 Redis 消息队列
它是消息能力的一种,但没有默认持久化、ACK、重试和消费进度,不能直接等同于可靠队列。
误区二:PUBLISH 返回 1,业务一定成功
它只说明 Redis 找到了符合条件的本地客户端连接,不代表客户端业务处理完成。
误区三:订阅者重连后能补回断线消息
自动重连只能恢复未来订阅,过去的消息已经消失。
误区四:不同 DB 的同名 Channel 互相隔离
Channel 不属于 Key Space,数据库编号不会隔离 Pub/Sub。
误区五:同一消息一个客户端最多收到一次
直接频道和多个模式同时命中时,同一连接可能收到多份。
误区六:RESP3 可以复用连接,所以一定要这么做
协议允许不代表工程上更清晰。专用订阅连接通常更方便隔离和恢复。
误区七:Cluster 中 PUBLISH 返回 0 就是无人订阅
返回值只统计发布节点本地客户端,远端节点订阅者仍可能收到全局消息。
误区八:Sharded Pub/Sub 会把一条频道自动复制到所有分片
它恰恰是为了限制传播范围。频道通过 Slot 归属一个分片,不会把同一条分片消息广播给全部分片。
十六、上线前检查清单
- 业务能接受断线期间丢消息;
- Channel 包含环境和业务前缀;
- 发布端与订阅端使用相同命令族;
- 客户端使用专用订阅连接并实现重连重订阅;
- 回调处理足够快,重任务已移交受控线程池;
- 消息体保持小巧,只包含 ID、版本和必要字段;
- 模式订阅不会与精确订阅大量重叠;
- 业务处理可以容忍或消除重复事件;
- 监控连接数量、重连、
omem、处理失败和端到端延迟; - 核心状态另有权威存储或定时校验;
- Cluster 大规模广播已评估 Sharded Pub/Sub;
- 必须可靠的事件已经改用 Stream 或专业消息队列。
十七、总结:广播负责快,台账负责可靠
回到开头的城市广播站:
PUBLISH是主持人开口;- Channel 是广播频率;
SUBSCRIBE是收音机调台;PSUBSCRIBE是按规则自动搜台;- 离线收音机听不到过去的节目;
- 慢听众会让输出缓冲区积压,最终可能被断开;
- Cluster 全局 Pub/Sub 是全城广播;
- Sharded Pub/Sub 是分区广播,减少集群内部扩散。
Redis Pub/Sub 的优势不是“什么都保证”,而是用极低的理解和使用成本,把一条瞬时消息实时推给多个在线消费者。
只要你记住它没有历史、没有签收、没有自动补发,就不会把城市广播站误当成快递仓库。
广播负责快,台账负责可靠。该让谁做什么,边界清楚,系统才不会在半夜突然“全城失联”。
参考资料
- Redis Pub/Sub 官方文档
- PUBLISH 命令与 Cluster 返回值说明
- SUBSCRIBE 命令
- PSUBSCRIBE 命令
- SSUBSCRIBE 命令
- Redis 客户端处理与输出缓冲区限制
- Redis Cluster 规范
- 个人小游戏


更多推荐


所有评论(0)