什么是发布与订阅?
发布与订阅(pub/sub)是一种消息通信模式,目的是解耦消息发布者和消息订阅者之间的耦合,redis作为一个发布/订阅的服务器,在发布者和订阅者之间起到了消息路由的功能。Redis中的消息类型叫做通道(channel),一个客户端可以订阅多个通道,也可以向多个通道发送消息。
如下图所示,加入有3个客户端(客户端A、客户端B、客户端C)订阅了频道1,那么当客户端1向频道1发送消息时,3个客户端都会收到消息。
频道的订阅与退订
命令:subscribe channel [channel ...],客户端执行该命令订阅某个或某些频道。
Redis将所有频道的订阅关系都保存到服务器pubsub_channels字典中,这个字典的键是某个被订阅的频道,值则是一个链表,链表中记录所有订阅这个频道的客户端。如下图所示,客户端A订阅了频道1,客户端B和客户端C订阅了频道2,客户端D、客户端E和客户端F订阅了频道3。
如果此时客户端A退订了频道1,订阅频道2,频道1处于没有客户端订阅状态,这是频道1的订阅关系会从pubsub+channels字典表中删除,客户端A会加到信息会保存到频道2的订阅关系链表中。
举个简单的例子(就使用上一篇文章中的128和129两台服务器)
使用xshell工具,启动在129上的redis服务,然后打开3个窗口,依次记为窗口1、窗口2、窗口3
在窗口2和窗口3中订阅频道,如下所示
窗口2:
127.0.0.1:6379> subscribe channel.1 channel.2
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel.1"
3) (integer) 1
1) "subscribe"
2) "channel.2"
3) (integer) 2
窗口3:
127.0.0.1:6379> subscribe channel.1 channel.3
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel.1"
3) (integer) 1
1) "subscribe"
2) "channel.3"
3) (integer) 2
此时,在窗口1中执行发布消息命令,窗口1:
127.0.0.1:6379> publish channel.1 hhhh
(integer) 2
127.0.0.1:6379> publish channel.2 aaaaa
(integer) 1
127.0.0.1:6379> publish channel.3 bbbbb
(integer) 1
窗口2:
127.0.0.1:6379> subscribe channel.1 channel.2
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel.1"
3) (integer) 1
1) "subscribe"
2) "channel.2"
3) (integer) 2
1) "message"
2) "channel.1"
3) "hhhh"
1) "message"
2) "channel.2"
3) "aaaaa"
窗口3:
127.0.0.1:6379> subscribe channel.1 channel.3
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel.1"
3) (integer) 1
1) "subscribe"
2) "channel.3"
3) (integer) 2
1) "message"
2) "channel.1"
3) "hhhh"
1) "message"
2) "channel.3"
3) "bbbbb"
当然,你也可以打开128(129的主服务器),在128中打开redis命令窗口,记为窗口4,在窗口4中发布消息,值得注意的是返回值为0,但是129上是可以收到消息的;你反过来测试下回发现,在129上发布消息,128上收不到。个人理解:128是129的主服务器,订阅命令其实质是一个写命令,而主服务器的写命令会同步到从服务器,也就是说其实在129上也会发布消息,这样就能够解释在128上发布的消息,返回值为0,但是129上却能接收到消息了。
窗口4:
127.0.0.1:6379> publish channel.1 ceshiyixia
(integer) 0
127.0.0.1:6379> publish channel.2 biubiubiu
(integer) 0
127.0.0.1:6379> publish channel.3 qqqqqq
(integer) 0
窗口2:
127.0.0.1:6379> subscribe channel.1 channel.2
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel.1"
3) (integer) 1
1) "subscribe"
2) "channel.2"
3) (integer) 2
1) "message"
2) "channel.1"
3) "hhhh"
1) "message"
2) "channel.2"
3) "aaaaa"
1) "message"
2) "channel.1"
3) "ceshiyixia"
1) "message"
2) "channel.2"
3) "biubiubiu"
窗口3:
127.0.0.1:6379> subscribe channel.1 channel.3
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel.1"
3) (integer) 1
1) "subscribe"
2) "channel.3"
3) (integer) 2
1) "message"
2) "channel.1"
3) "hhhh"
1) "message"
2) "channel.3"
3) "bbbbb"
1) "message"
2) "channel.1"
3) "ceshiyixia"
1) "message"
2) "channel.3"
3) "qqqqqq"
129上发布消息,128上接收
窗口4:
127.0.0.1:6379> subscribe channel.5
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel.5"
3) (integer) 1
窗口1:
127.0.0.1:6379> publish channel.5 1111
(integer) 0
窗口1发布了消息,但是128上不回接收到消息。
命令:unsubscribe channel [channel ...],客户端执行该命令退订某个频道。
在窗口3中使用ctrl+c退出订阅模式,然后再进入redis命令模式,使用unsubscribe命令进行退订。
^C
[root@localhost src]# ./redis-cli
127.0.0.1:6379> unsubscribe channel1.3
1) "unsubscribe"
2) "channel1.3"
3) (integer) 0
这里存在一个有意思的问题,首先还原下窗口3,如下所示
127.0.0.1:6379> subscribe channel.1 channel.3
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel.1"
3) (integer) 1
1) "subscribe"
2) "channel.3"
3) (integer) 2
重新打开一个129的窗口--窗口5,使用pubsub channels命令,查看已经订阅的频道
127.0.0.1:6379> pubsub channels
1) "channel.2"
2) "channel.1"
3) "channel.3"
然后切换到窗口3,使用ctrl+c退出订阅模式
127.0.0.1:6379> subscribe channel.1 channel.3
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel.1"
3) (integer) 1
1) "subscribe"
2) "channel.3"
3) (integer) 2
^C
[root@localhost src]#
再次在窗口5中执行pubsub channels命令,查看已经订阅的频道
127.0.0.1:6379> pubsub channels
1) "channel.2"
2) "channel.1"
然后在窗口3中执行退订命令
^C
[root@localhost src]# ./redis-cli
127.0.0.1:6379> unsubscribe channel1.3
1) "unsubscribe"
2) "channel1.3"
3) (integer) 0
窗口5显示结果
127.0.0.1:6379> pubsub channels
1) "channel.2"
2) "channel.1"
官网上unsubscribe命令是用来退订频道的,所以不需要质疑这条命令的准确性。但是你就会发现,其实对于channel1.3来说,在窗口3中退出订阅时已经关闭了对这个频道的订阅,与退订的命令unsubscribe channel1.3并没有关系。
希望有知道原因的前辈可以把原因写到评论区,非常感谢!
模式的订阅与退订
模式的订阅关系保存到pubsub_patterns属性中,它是一个链表,链表中每一个节点都包含一个PubsubPattern结构,这个结构的pattern属性记录了被订阅的模式,client属性记录了订阅模式的客户端。
PubsubPattern结构:
PubsubPattern |
Client 客户端A |
Pattern 模式a |
它表示的是客户端A订阅模式a。
命令:psubscribe pattern [pattern ...],订阅模式,执行psubscribe命令时,生成一个PubsubPattern结构,并将这个结构添加到pubsub_patterns链表中;
举个例子:
打开128上3个redis的命令窗口,分别记为窗口A、窗口B、窗口C;
窗口B订阅模式name*(以name开头)
127.0.0.1:6379> psubscribe name*
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "name*"
3) (integer) 1
窗口C订阅模式score*(已score开头)
127.0.0.1:6379> psubscribe score*
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "score*"
3) (integer) 1
窗口A中发布消息
127.0.0.1:6379> publish name_su hhh
(integer) 1
127.0.0.1:6379> publish name_zhang zzzz
(integer) 1
127.0.0.1:6379> publish score.su 92
(integer) 1
127.0.0.1:6379> publish score.zhang 85
(integer) 1
此时,窗口B
127.0.0.1:6379> psubscribe name*
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "name*"
3) (integer) 1
1) "pmessage"
2) "name*"
3) "name_su"
4) "hhh"
1) "pmessage"
2) "name*"
3) "name_zhang"
4) "zzzz"
窗口C
127.0.0.1:6379> psubscribe score*
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "score*"
3) (integer) 1
1) "pmessage"
2) "score*"
3) "score.su"
4) "92"
1) "pmessage"
2) "score*"
3) "score.zhang"
4) "85"
命令:punsubscribe [pattern [pattern ...]],退订模式,将对应的PubsubPattern结构从链表中删除。这里不做描述,可以参考下上面频道的退订。
命令:publish channel message,发布消息。这个命令在上面例子中已经使用到了,当redis客户端执行这个命令时,将消息(message)发送到频道(channel),消息会发送给频道的所有订阅者,另外,如果有模式与频道匹配,则消息还会发送给所有的模式订阅者。这个命令的使用可以看上面例子,这里不再做描述。
命令:pubsub subcommand [argument [argument ...]],客户端可以通过这个命令来查看频道或者模式的相关信息。
pubsub channels :查询当前订阅频道;
pubsub numsub :查询任意多个频道的订阅者数量;
pubsub numpat :查询当前被订阅模式的数量。
举个例子,128上打开3个窗口,分别记为a,b,c
窗口b中订阅频道
127.0.0.1:6379> subscribe name.su name.liu
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "name.su"
3) (integer) 1
1) "subscribe"
2) "name.liu"
3) (integer) 2
窗口c中订阅模式
127.0.0.1:6379> psubscribe name.* score.*
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "name.*"
3) (integer) 1
1) "psubscribe"
2) "score.*"
3) (integer) 2
窗口a中查看订阅频道
pubsub channels:查看所有订阅频道;pubsub channels pattern:查看所有满足pattern的订阅频道
127.0.0.1:6379> pubsub channels
1) "name.liu"
2) "name.su"
127.0.0.1:6379> pubsub channels name.s*
1) "name.su"
pubsub numsub :查询任意多个频道的订阅者数量
127.0.0.1:6379> pubsub numsub name.su name.xie name.liu
1) "name.su"
2) (integer) 1
3) "name.xie"
4) (integer) 0
5) "name.liu"
6) (integer) 1
pubsub numpat :查询当前被订阅模式的数量
127.0.0.1:6379> pubsub numpat
(integer) 2
以上就是redis的发布与订阅。
如果有写的不对的地方,请大家多多批评指正,非常感谢!