文章目录


晚上十一点,城市广播站突然收到一条通知:跨江大桥临时封闭。

主持人拿起话筒,在“城市交通”频道里播报:

前方桥梁检修,请车辆绕行滨江路。

正在收听这个频道的出租车、公交调度室和居民收音机,几乎同时听到了消息。广播站不用知道听众是谁,也不用挨个给他们打电话;听众同样不需要认识主持人,只要调到正确频率即可。

这就是发布/订阅模式,也是 Redis Pub/Sub 最适合解决的问题:让消息生产者和在线消费者通过频道解耦,并把一条消息实时推送给多个订阅者。

不过,广播也有一个致命特点:收音机没开机,刚才那段路况就错过了。节目不会因为你迟到而自动倒带,更没有“签收回执”帮你确认每位听众都听懂了。

所以 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 分片内传播

城市广播站角色与 Redis Pub/Sub 概念对应

图 2:Publisher 不认识 Subscriber,双方只约定 Channel 名称。

这里最重要的不是命令,而是两个“互相不知道”:

  • 发布者不知道有多少订阅者,也不需要维护订阅者地址;
  • 订阅者不知道消息由哪个发布者产生,只关心自己订阅的频道。

这种解耦让临时通知非常方便。比如配置刷新、聊天室在线消息、服务状态广播、实时比分和运维告警,都可以先想到 Pub/Sub。

但它只解耦了发送关系,并没有自动提供持久化、确认、重试、消费进度和死信队列。

二、一条广播到底怎么发出去

准备两个终端,先让第一个终端订阅交通频道:

redis-cli -p 6403 SUBSCRIBE city:traffic

Redis 会先推送一条订阅确认:

1) "subscribe"
2) "city:traffic"
3) (integer) 1

三个字段可以理解为:

  1. 事件类型是 subscribe
  2. 成功订阅的频道是 city:traffic
  3. 当前连接一共订阅了一个频道或模式。

然后在第二个终端发布路况:

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,连接就进入订阅状态。这个连接只能执行订阅相关命令、PINGRESETQUIT 等少量命令,不能再像普通连接一样随意 GETSET

原因很好理解:服务器正在不断向这条连接主动推送消息,普通请求响应与推送事件如果没有更强的协议区分,客户端很难安全复用。

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,而不是广播给所有分片。

Cluster 全局 Pub/Sub 与 Sharded Pub/Sub 分区广播

图 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 全局和分片命令不能混着用

SUBSCRIBEPUBLISH 是一组,SSUBSCRIBESPUBLISH 是另一组。订阅 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:空闲时间;
  • subpsubssub:订阅数量;
  • 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:把它想成一座有签收回执的快递分拣中心

十四、线上故障怎么排查

Redis Pub/Sub 无订阅者、断线、慢消费者与技术选型排查

图 6:先判断“没人收听、听众掉线、听众太慢,还是根本选错了工具”。

14.1 发布返回 0

依次检查:

  1. Subscriber 是否已经完成订阅确认;
  2. 频道名称、大小写和环境前缀是否一致;
  3. 发布端使用的是 PUBLISH 还是 SPUBLISH
  4. Cluster 中是否只是远端订阅者没有计入返回值;
  5. 客户端是否正在重连窗口;
  6. ACL 是否允许访问该频道。

14.2 有订阅连接,但业务没有反应

检查:

  • 客户端事件循环是否被阻塞;
  • 回调是否异常退出;
  • 消息格式或版本是否兼容;
  • 订阅线程是否把工作丢进已经满了的线程池;
  • CLIENT LIST TYPE pubsubomem 是否持续增大;
  • 日志中是否出现输出缓冲区超限导致的断开。

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 的优势不是“什么都保证”,而是用极低的理解和使用成本,把一条瞬时消息实时推给多个在线消费者

只要你记住它没有历史、没有签收、没有自动补发,就不会把城市广播站误当成快递仓库。

广播负责快,台账负责可靠。该让谁做什么,边界清楚,系统才不会在半夜突然“全城失联”。

参考资料


  • 个人小游戏
    个人小游戏
    在这里插入图片描述
Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐