发布订阅模式
发布订阅模式(pubsub pattern)中,消息发布者(publisher)发布消息(message)到一个频道(channel/topic),订阅者(subscriber)订阅其中的频道。当发布者发布消息到一个频道时,所有的订阅这个频道的订阅者都会收到这个消息。
生产者/消费者模式
生产消费模式和发布订阅的不同之处在于,消息只能被一个消费者消费。
例子
- 客户端1发布消息:
127.0.0.1:6379> publish info hello //向info频道发送消息
(integer) 1 //返回订阅info频道的订阅者的数量
127.0.0.1:6379> publish info hi
(integer) 1
127.0.0.1:6379>
- 客户端2订阅频道:
127.0.0.1:6379> subscribe info //订阅频道info
Reading messages... (press Ctrl-C to quit)
1) "subscribe" //订阅频道返回的消息,表示订阅成功,消息又三部分组成,第一是“subscribe”字符串,表示这是一个订阅消息,第二是定于频道的名字,第三是当前订阅频道数量
2) "info"
3) (integer) 1
1) "message" //消息发布者的消息,消息由三部分组成,第一是“message”字符串,表示这是一个消息,第二是定于频道的名字,第三是消息内容
2) "info"
3) "hello"
1) "message"
2) "info"
3) "hi
命令
命令 | 解析 |
---|---|
publish channel message | 发布消息到指定频道 |
subscribe channel [channel...] | 订阅一或者多个频道 |
psubscribe pattern [pattern...] | 模式订阅,pattern是一个正则表达式,称之为模式,利用这个正则可以订阅与之匹配的频道 |
unsubscribe channel [channel...] | 取消订阅 |
punsubscribe pattern [pattern...] | 取消模式订阅 |
pubsub <subcommand> [argument [argument...]] | 自检命令,用来检查发布订阅的状态,如当前有多少频道,频道下有多少订阅者 |
两种订阅方式:频道订阅和模式订阅
消息的订阅有两种方式,一种是订阅频道(看例子章节),另一种是订阅一个模式(正则),如果一个消息的频道名字和这个模式匹配,这个消息就会发送给订阅了这个模式的订阅者。
- 客户端1向info和information两个频道发送消息:
127.0.0.1:6379> publish info 'how are you'
(integer) 2
127.0.0.1:6379> publish information 'im information channel!!'
(integer) 1
127.0.0.1:6379>
- 客户端2向redis服务器订阅了inf*这个模式:
127.0.0.1:6379>
127.0.0.1:6379> psubscribe inf*
Reading messages... (press Ctrl-C to quit)
1) "psubscribe" //psubscribe消息,表示订阅成功
2) "inf*" //模式订阅
3) (integer) 1 //当前只订阅了一个模式
1) "pmessage" //pmessage表示这是一个普通消息
2) "inf*" //消息对应的pattern
3) "info" //那个频道发送过来的,显然info匹配inf*这个正则表达式
4) "how are you" //消息内容
1) "pmessage"
2) "inf*"
3) "information"
4) "im information channel!!"
如果订阅者订阅了多个模式,一个消息的频道与几个模式都匹配的话,订阅者就会收到多个消息。
127.0.0.1:6379> psubscribe i*f in* *for* //同时订阅三个模式
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "i*f"
3) (integer) 1
1) "psubscribe"
2) "in*"
3) (integer) 2
1) "psubscribe"
2) "*for*"
3) (integer) 3 //总共订阅三个模式
1) "pmessage" //消息A
2) "*for*"
3) "information"
4) "hello"
1) "pmessage" //消息A,一个消息收到了两次
2) "in*"
3) "information"
4) "hello"
订阅关系的底层实现
以下是redis源码server.h中声明的保存订阅关系的变量,从中不难看出,用subscrib命令订阅的关系放在字典,而模式订阅放在一个列表中。
dict *pubsub_channels; /* channels a client is interested in (SUBSCRIBE) */
list *pubsub_patterns; /* patterns a client is interested in (SUBSCRIBE) */
redis中使用subscribe命令订阅频道,客户端与频道的订阅关系被保存在一个名为pubsub_channels的字典里面。当有客户端订阅频道时,会将对应的信息保存到字典的对应的链表中。取消订阅时,redis会将对应的键值对从字典中删除。当发布消息时,将channel哈希定位到具体的链表,给里面对应的客户端发送消息。
模式订阅的订阅关系被保存在一个链表中,链表中的每个结点都是一个pubsubPattern结构体
typedef struct pubsubPattern {
client *client;
robj *pattern;
} pubsubPattern;