-
Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。
-
订阅者可以订阅任意数量的频道。
-
Redis被广泛用于构建即时通信应用,比如网络聊天室(chatroom)和实时广播、实时提醒等。
发布订阅的常用指令
序号 | 命令及描述 |
---|---|
1 | subscribe channel [channel ...] 订阅给定的一个或多个频道的信息。 |
2 | psubscribe pattern [pattern ...] 订阅一个或多个符合给定模式的频道。(模糊订阅) |
3 | publish channel message 将信息发送到指定的频道。 |
4 | unsubscribe [channel [channel ...]] 指退订给定的频道。 |
5 | punsubscribe[pattern [pattern ...]] 退订所有给定模式的频道。(模糊退订) |
6 | pubsub subcommand [argument [argument ...]] 查看订阅与发布系统状态。 |
发布订阅的实现
subscribe channel [channel …] 订阅频道
- 当一个频道第一次被订阅时,这个频道就被自动创建。
# 1窗口
127.0.0.1:6379> subscribe chat news.nongye news.dili
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "chat"
3) (integer) 1
1) "subscribe"
2) "news.nongye"
3) (integer) 2
1) "subscribe"
2) "news.dili"
3) (integer) 3
psubscribe pattern [pattern …]订阅符合要求的频道
- 通过
*
的方式来指定某个模式的频道,比如new.*
来指定所有new.
开头的频道。
# 2窗口
127.0.0.1:6379> psubscribe news.* # 订阅具有new.开头的所有频道
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "news.*"
3) (integer) 1
publish channel message 发送信息到指定频道
向指定频道发送信息
# 3号窗口
127.0.0.1:6379> publish news.dili kuangshi
(integer) 2
127.0.0.1:6379> publish news.nongye daocao
(integer) 2
127.0.0.1:6379> publish chat hello
(integer) 1
订阅者接收信息
# 1号窗口
1) "message"
2) "news.dili"
3) "kuangshi"
1) "message"
2) "news.nongye"
3) "daocao"
1) "message"
2) "chat"
3) "hello"
# 2号窗口
1) "pmessage"
2) "news.*"
3) "news.dili"
4) "kuangshi"
1) "pmessage"
2) "news.*"
3) "news.nongye"
4) "daocao"
unsubscribe [channel [channel …]] 退订指定的频道
punsubscribe[pattern [pattern …]] 退订指定模式的频道
- 当退出订阅发布模式时,会自动退订所有已订阅频道
- unsubscribe命令不能应用于redis-cli客户端中,因为redis-cli运行订阅后,客户端处于阻塞模式,
只能通过Ctrl-C退出订阅模式
,而如果使用第三方组件则可以实现这一功能。 - punsubscribe也是使用
punsubscribe new.*
的命令形式来使用的。
# 这里对unsubscribe进行一个简单的模拟
redis 127.0.0.1:6379> UNSUBSCRIBE mychannel
1) "unsubscribe"
2) "a"
3) (integer) 0
pubsub subcommand [argument [argument …]] 查看订阅与发布系统状态
- pubsub channels [pattern] 查看指定模式频道的活跃频道(至少要两以上订阅者)
- pubsub numsub [channel-1 … channel-N] 查看指定频道的订阅者数量
- pubsub numpat 查看频道数目
# 查看指定模式频道的活跃频道
127.0.0.1:6379> pubsub channels news.*
1) "news.dili"
2) "news.nongye"
# 查看指定频道的订阅者数量
127.0.0.1:6379> pubsub numsub news.dili news.nongye chat
1) "news.dili"
2) (integer) 2
3) "news.nongye"
4) (integer) 2
5) "chat"
6) (integer) 1
# 查看频道数目
127.0.0.1:6379> pubsub numpat
(integer) 3
订阅发布原理解析
在publish.c
文件夹存在着发布订阅模式的完整源码,这里对发布订阅作简单描述。
在 Redis 的底层结构中,Redis 服务器结构体中定义了一个 pubsub_channels
字典
struct redisServer {
//用于保存所有频道的订阅关系
dict *pubsub_channels;
}
这个字典中,key 代表的是频道名称,value 是一个链表,这个链表里面存放的是所有订阅这个频道的客户端。
// 订阅频道,传入订阅者和频道
int pubsubSubscribeChannel(client *c, robj *channel) {
dictEntry *de;
list *clients = NULL;
int retval = 0;
/* Add the channel to the client -> channels hash table */
// 更新字典的键值
if (dictAdd(c->pubsub_channels,channel,NULL) == DICT_OK) {
retval = 1;
incrRefCount(channel);
/* Add the client to the channel -> list of clients hash table */
// 更新链表中的结点
de = dictFind(server.pubsub_channels,channel);
if (de == NULL) {
clients = listCreate();
dictAdd(server.pubsub_channels,channel,clients);
incrRefCount(channel);
} else {
clients = dictGetVal(de);
}
listAddNodeTail(clients,c);
}
/* Notify the client */
addReplyPubsubSubscribed(c,channel);
return retval;
}
int pubsubPublishMessage(robj *channel, robj *message) {
//传入频道和消息
int receivers = 0;
dictEntry *de;
dictIterator *di;
listNode *ln;
listIter li;
/* Send to clients listening for that channel */
// 找到频道,遍历出订阅者并发布信息
de = dictFind(server.pubsub_channels,channel);
if (de) {
list *list = dictGetVal(de);
listNode *ln;
listIter li;
listRewind(list,&li);
while ((ln = listNext(&li)) != NULL) {
client *c = ln->value;
addReplyPubsubMessage(c,channel,message);
receivers++;
}
}
/* Send to clients listening to matching channels */
// 找到模式化订阅的订阅者,逻辑更为复杂
di = dictGetIterator(server.pubsub_patterns);
if (di) {
channel = getDecodedObject(channel);
while((de = dictNext(di)) != NULL) {
robj *pattern = dictGetKey(de);
list *clients = dictGetVal(de);
if (!stringmatchlen((char*)pattern->ptr,
sdslen(pattern->ptr),
(char*)channel->ptr,
sdslen(channel->ptr),0)) continue;
listRewind(clients,&li);
while ((ln = listNext(&li)) != NULL) {
client *c = listNodeValue(ln);
addReplyPubsubPatMessage(c,pattern,channel,message);
receivers++;
}
}
decrRefCount(channel);
dictReleaseIterator(di);
}
return receivers;
}