Redis 发布/订阅
如何在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的发布/订阅展示的是最多一次的消息传递语义。 顾名思义,这意味着消息最多只会被传递一次。 一旦消息由Redis服务器发送,就没有再次发送的机会。 如果订阅者无法处理消息(例如,由于错误或网络断开),消息将永远丢失。
如果你的应用程序需要更强的交付保证,你可能想了解Redis Streams。流中的消息是持久化的,并且支持最多一次以及至少一次的交付语义。
推送消息的格式
消息是一个包含三个元素的数组回复。
第一个元素是消息的类型:
-
subscribe: 表示我们成功订阅了作为回复中第二个元素给出的频道。 第三个参数表示我们当前订阅的频道数量。 -
unsubscribe: 表示我们成功从回复中作为第二个元素给出的频道退订。 第三个参数表示我们当前订阅的频道数量。 当最后一个参数为零时,我们不再订阅任何频道,客户端可以发出任何类型的Redis命令,因为我们处于Pub/Sub状态之外。 -
message: 它是作为另一个客户端发出的PUBLISH命令的结果接收到的消息。 第二个元素是来源通道的名称,第三个参数是实际的消息负载。
数据库与作用域
Pub/Sub 与键空间没有关系。 它的设计是为了在任何层面上都不干扰键空间,包括数据库编号。
在数据库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的发布/订阅实现支持模式匹配。 客户端可以订阅全局样式模式,以接收发送到与给定模式匹配的频道名称的所有消息。
例如:
PSUBSCRIBE news.*
将接收发送到频道 news.art.figurative, news.music.jazz 等的所有消息。
所有通配符样式的模式都是有效的,因此支持多个通配符。
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 开始,引入了分片 Pub/Sub,其中分片通道通过用于将键分配到槽的相同算法分配到槽中。
分片消息必须发送到拥有分片通道哈希到的槽的节点。
集群确保发布的分片消息被转发到分片中的所有节点,因此客户端可以通过连接到负责槽的主节点或其任何副本来订阅分片通道。
SSUBSCRIBE、SUNSUBSCRIBE 和 SPUBLISH 用于实现分片 Pub/Sub。
分片发布/订阅有助于在集群模式下扩展发布/订阅的使用。 它限制消息在集群的分片内传播。 因此,与全局发布/订阅相比,通过集群总线传递的数据量是有限的,在全局发布/订阅中,每条消息都会传播到集群中的每个节点。 这允许用户通过添加更多分片来水平扩展发布/订阅的使用。
编程示例
Pieter Noordhuis 提供了一个使用 EventMachine 和 Redis 创建 多用户高性能网络聊天 的绝佳示例。
客户端库实现提示
因为所有接收到的消息都包含导致消息传递的原始订阅(在消息类型的情况下是通道,在pmessage类型的情况下是原始模式),客户端库可以使用哈希表将原始订阅绑定到回调(可以是匿名函数、块、函数指针)。
当接收到消息时,可以进行O(1)查找以将消息传递给已注册的回调函数。