Redis Pub/Sub
如何在 Redis 中使用发布/订阅通道
SUBSCRIBE
、UNSUBSCRIBE
和 PUBLISH
实现了发布/订阅消息模式,其中(引用维基百科)发送者(发布者)无需知道特定的接收者(订阅者)是谁即可发送消息。相反,发布的消息会被归入不同的通道,而无需知道是否存在(或有哪些)订阅者。订阅者表达对一个或多个通道的兴趣,只接收他们感兴趣的消息,而无需知道是否存在(或有哪些)发布者。发布者和订阅者之间的这种解耦实现了更高的可伸缩性和更动态的网络拓扑。
例如,要订阅通道 "channel11" 和 "ch:00",客户端发出 SUBSCRIBE
命令并提供通道名称。
SUBSCRIBE channel11 ch:00
其他客户端发送到这些通道的消息将由 Redis 推送给所有已订阅的客户端。订阅者按照消息发布的顺序接收消息。
订阅了一个或多个通道的客户端不应发出命令,尽管它可以 SUBSCRIBE
和 UNSUBSCRIBE
到或从其他通道。订阅和取消订阅操作的回复以消息的形式发送,以便客户端可以只读取一个连贯的消息流,其中第一个元素表示消息的类型。在已订阅的 RESP2 客户端上下文中允许使用的命令包括:
然而,如果使用 RESP3(参阅 HELLO
),客户端在订阅状态下可以发出任何命令。
请注意,当使用 redis-cli
时,在订阅模式下无法使用 UNSUBSCRIBE
和 PUNSUBSCRIBE
等命令,因为 redis-cli
将不接受任何命令,只能通过 Ctrl-C
退出该模式。
交付语义
Redis 的发布/订阅(Pub/Sub)表现出至多一次的消息交付语义。顾名思义,这意味着消息最多只会被交付一次。一旦消息由 Redis 服务器发送,它就没有再次发送的机会。如果订阅者无法处理消息(例如,由于错误或网络断开),消息将永远丢失。
如果你的应用需要更强的交付保证,你可能需要了解Redis Streams。流中的消息是持久化的,并且支持至多一次和至少一次的交付语义。
推送消息的格式
消息是一个包含三个元素的数组回复。
第一个元素是消息的类型
-
subscribe
:表示我们成功订阅了回复中作为第二个元素给出的通道。第三个参数表示我们当前订阅的通道数量。 -
unsubscribe
:表示我们成功取消订阅了回复中作为第二个元素给出的通道。第三个参数表示我们当前订阅的通道数量。当最后一个参数为零时,表示我们不再订阅任何通道,客户端可以发出任何类型的 Redis 命令,因为我们已退出发布/订阅状态。 -
message
:这是由于其他客户端发出的PUBLISH
命令而接收到的消息。第二个元素是源通道的名称,第三个参数是实际的消息载荷。
数据库和范围
发布/订阅与键空间没有关系。它的设计是为了在任何层面(包括数据库编号)都不对其产生干扰。
在数据库 10 上发布的消息,将被数据库 1 上的订阅者听到。
如果你需要某种范围划分,可以用环境名称(测试、预生产、生产等)作为通道的前缀。
有线协议示例
SUBSCRIBE first second
*3
$9
subscribe
$5
first
:1
*3
$9
subscribe
$6
second
:2
此时,从另一个客户端,我们对名为 second
的通道发出一个 PUBLISH
操作。
> PUBLISH second Hello
这是第一个客户端接收到的内容
*3
$7
message
$6
second
$5
Hello
现在客户端使用不带附加参数的 UNSUBSCRIBE
命令取消订阅所有通道。
UNSUBSCRIBE
*3
$11
unsubscribe
$6
second
:1
*3
$11
unsubscribe
$5
first
:0
模式匹配订阅
Redis 发布/订阅实现支持模式匹配。客户端可以订阅 glob 风格的模式,以接收发送到匹配给定模式的通道名称的所有消息。
例如
PSUBSCRIBE news.*
将接收发送到通道 news.art.figurative
、news.music.jazz
等的所有消息。所有 glob 风格的模式都是有效的,因此支持多个通配符。
PUNSUBSCRIBE news.*
然后将客户端从该模式取消订阅。此调用不会影响其他任何订阅。
通过模式匹配接收到的消息以不同的格式发送
- 消息类型是
pmessage
:这是由于其他客户端发出的PUBLISH
命令而接收到的消息,该消息匹配了模式匹配订阅。第二个元素是匹配到的原始模式,第三个元素是源通道的名称,最后一个元素是实际的消息载荷。
与 SUBSCRIBE
和 UNSUBSCRIBE
类似,PSUBSCRIBE
和 PUNSUBSCRIBE
命令通过系统发送类型为 psubscribe
和 punsubscribe
的消息来确认,其格式与 subscribe
和 unsubscribe
消息格式相同。
匹配模式和通道订阅的消息
如果客户端订阅了匹配已发布消息的多个模式,或者同时订阅了匹配消息的模式和通道,客户端可能会多次接收同一条消息。下面的示例对此进行了说明:
SUBSCRIBE foo
PSUBSCRIBE f*
在上面的示例中,如果向通道 foo
发送一条消息,客户端将收到两条消息:一条类型为 message
,另一条类型为 pmessage
。
模式匹配中订阅数量的含义
在 subscribe
、unsubscribe
、psubscribe
和 punsubscribe
消息类型中,最后一个参数是仍然处于活动状态的订阅数量。此数字是客户端仍订阅的通道和模式的总数。因此,只有当由于取消订阅所有通道和模式导致此计数降至零时,客户端才会退出发布/订阅状态。
分片发布/订阅
从 Redis 7.0 开始引入了分片发布/订阅,其中分片通道通过与分配键到槽相同的算法分配到槽。分片消息必须发送到拥有该分片通道哈希到的槽的节点。集群会确保已发布的分片消息转发到分片中的所有节点,因此客户端可以通过连接负责该槽的主节点或其任何副本节点来订阅分片通道。SSUBSCRIBE
、SUNSUBSCRIBE
和 SPUBLISH
用于实现分片发布/订阅。
分片发布/订阅有助于在集群模式下扩展发布/订阅的使用。它将消息的传播限制在集群的分片范围内。因此,与全局发布/订阅相比,通过集群总线传输的数据量是有限的,在全局发布/订阅中,每条消息都会传播到集群中的每个节点。这允许用户通过添加更多分片来横向扩展发布/订阅的使用。
编程示例
Pieter Noordhuis 提供了一个很好的示例,使用 EventMachine 和 Redis 创建了一个多用户高性能网络聊天室。
客户端库实现提示
由于所有接收到的消息都包含导致消息交付的原始订阅(在消息类型中是通道,在 pmessage 类型中是原始模式),客户端库可以使用哈希表将原始订阅绑定到回调(可以是匿名函数、块、函数指针)。
当收到消息时,可以通过 O(1) 的查找将消息交付给已注册的回调函数。