21. 发布与订阅 Pub/Sub
本教程共 40 篇 · 第 21 篇 · 更新于 2026-08-02
21. 发布与订阅 Pub/Sub
发布/订阅(Publish/Subscribe,常缩写为 Pub/Sub)是一种经典的消息通信模式:发送者(发布者 publisher)把消息发到某个“频道(channel)”,但并不关心谁在接收;接收者(订阅者 subscriber)只声明自己关心哪些频道,有消息就被动收到。发布者和订阅者彼此解耦,构建出松耦合、易扩展的消息拓扑。
Redis 内置了轻量的 Pub/Sub 实现,无需额外中间件就能实现进程间实时通知。
本节目标
- 掌握
PUBLISH/SUBSCRIBE/UNSUBSCRIBE三个核心命令的用法。 - 理解模式订阅
PSUBSCRIBE以及一条消息可能被收到多次的情况。 - 明白 Pub/Sub 的 at-most-once(最多一次) 投递语义及其隐患。
- 知道 Pub/Sub 与 key 空间、数据库编号无关这一特性。
- 了解集群环境下的分片 Pub/Sub(Sharded Pub/Sub)与功能局限性。
基本发布与订阅
sSUBSCRIBE 让当前连接订阅一个或多个频道。订阅成功后,该连接进入“订阅态”,之后来自其他客户端的消息会被实时推送过来。
为了演示,需要开两个 redis-cli 窗口。
第一个窗口(订阅者):
127.0.0.1:6379> SUBSCRIBE news.chat
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "news.chat"
3) (integer) 1
第二个窗口(发布者)向该频道发两条消息:
127.0.0.1:6379> PUBLISH news.chat "hello redis"
(integer) 1
127.0.0.1:6379> PUBLISH news.chat "learn pubsub"
(integer) 1
此时第一个窗口会立刻收到:
1) "message"
2) "news.chat"
3) "hello redis"
1) "message"
2) "news.chat"
3) "learn pubsub"
注意推送消息的数组结构:"message" 表示类型,第二项是频道名,第三项是消息内容。PUBLISH 的返回值是收到消息的订阅者数量(不含模式订阅),上例返回 1 表示有 1 个订阅者收到了。
退出订阅用 UNSUBSCRIBE(不带参数退订所有频道)。需要提醒:在 redis-cli 的订阅态里,除 Ctrl-C 外无法输入交互命令,这是 cli 工具的限制,真实客户端库不受影响。
模式订阅
除了精确订阅频道,还可以用 PSUBSCRIBE 按 glob 风格模式订阅一类频道。例如订阅所有 news. 开头的频道:
127.0.0.1:6379> PSUBSCRIBE news.*
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "news.*"
3) (integer) 1
之后无论谁向 news.sport、news.tech 发消息,这个订阅者都能收到,且收到的是 pmessage 类型(多了“匹配到的模式”字段)。
有一点容易踩坑:如果一个客户端同时精确订阅了某频道、又用模式订阅了能匹配该频道的模式,那么一条消息会被它收到两次(一次 message、一次 pmessage)。这是预期行为,设计订阅关系时要留意避免重复处理。
投递语义与局限性
Redis Pub/Sub 提供的是 at-most-once(最多一次) 投递:消息一旦由服务器发出,就不会再重发。如果订阅者当时不在线、或处理过程中崩溃、或网络断开,这条消息就永远丢失了。它不持久化、不缓冲、不保证可靠到达。
因此,Redis Pub/Sub 适合“实时通知、偶尔丢一两条无所谓”的场景,例如:
- 聊天室消息广播
- 配置变更实时通知各节点
- 简单的事件提醒
而不适合“每条消息都不能丢”的场景(如订单、交易)。这类需求应使用 Redis Stream(支持持久化与 at-least-once 语义)或专业消息队列。
另一个重要特性:Pub/Sub 与 key 空间完全无关,它不占用数据库、也不受 SELECT 选库影响。在 db 10 发布,db 1 的订阅者照样能收到。所以若需要环境隔离(测试/预发/生产),靠频道名加前缀来区分,而不是靠数据库编号。
集群中的分片 Pub/Sub
在 Redis Cluster 里,普通 Pub/Sub 的消息会在所有节点间广播,节点越多、带宽开销越大。从 Redis 7.0 起引入 分片 Pub/Sub(Sharded Pub/Sub):分片频道按和 key 一样的哈希算法分配到某个 slot,消息只在拥有该 slot 的“分片(主+其从)”内传播,从而可以水平扩展。相关命令是 SSUBSCRIBE / SUNSUBSCRIBE / SPUBLISH。
# 向分片频道发布(需在拥有对应 slot 的节点上执行)
127.0.0.1:6379> SPUBLISH shard.news "sharded msg"
(integer) 1
用 PUBSUB 命令查看状态
PUBSUB 子命令可查询系统状态,例如统计当前频道数、订阅者数:
127.0.0.1:6379> PUBSUB CHANNELS
1) "news.chat"
127.0.0.1:6379> PUBSUB NUMSUB news.chat
1) "news.chat"
2) (integer) 1
小结
SUBSCRIBE收、PUBLISH发、PSUBSCRIBE按模式收;一条消息可能被“精确+模式”双重订阅者收到两次。- 投递语义是 at-most-once,不持久化、丢失不补,不适合要求可靠到达的业务。
- Pub/Sub 与 key 空间、库编号无关,用频道前缀做环境隔离。
- 集群下用分片 Pub/Sub(
SPUBLISH等,Redis 7.0+)避免全集群广播开销。 - 需要持久化与可靠投递时,选 Redis Stream 而非 Pub/Sub。
实战经验
补充一点实战经验:频道命名推荐采用 业务:实体:事件 的分层结构,例如 order:created、user:login。这样既能用 PSUBSCRIBE order:* 做模式订阅、按业务批量接收,也方便用前缀做环境隔离(如 prod.order:* 与 test.order:*)。发布端与订阅端约定好命名规范,比事后排查“谁发了什么、为什么没收到”要轻松得多。
另外,订阅者断线重连后必须重新 SUBSCRIBE;成熟的客户端库通常封装了“断开自动重订”,但业务层要清楚:重订之前的消息已经丢了、无法补发。设计基于 Pub/Sub 的功能时,必须接受“瞬时断连 = 永久丢消息”这一事实,必要时用 Stream 做补偿。
常见误区
- 误区一:以为 Pub/Sub 消息会持久化。 它纯粹是内存里的实时转发,没有任何磁盘落盘。订阅者当时不在线、或处理到一半崩溃、或网络抖动断开,那条消息就永远消失了。
- 误区二:以为至少是“至少一次”投递。 实际是 at-most-once(最多一次):发出去就不管了,不重发、不缓冲、不确认。要求可靠到达的业务请用 Redis Stream 或专业消息队列。
- 误区三:以为
redis-cli订阅后能继续敲命令。 进入订阅态后,cli 只能接收推送、无法输入普通命令,只能用Ctrl-C退出。这是 cli 工具的限制,真实客户端库可以在订阅态用PING/ 继续PSUBSCRIBE等有限命令,使用 RESP3 协议时甚至能发任意命令。 - 误区四:以为不同数据库编号能隔离频道。 Pub/Sub 与 key 空间、库编号完全无关——在 db 10 发布,db 1 的订阅者照样收得到。环境隔离只能靠频道名加前缀(如
prod.news、test.news)。
提示:本教程统一基于 Redis 8.x(最新稳定版)行文。补充说明:官方 redis.io 下载页另标 8.8 为 “Latest stable”,而 GitHub 上的最新发布 tag 为 8.10.0,二者同属 8.x 大版本,对 Pub/Sub 命令与语义的讲述没有影响。