定义
一个或多个客户端订阅某个channel,当向该channel发布消息时,已经订阅该channel的所有客户端均会收到消息。channel通常称为频道。
类型
基于频道(channel)
命令
- 订阅
SUBSCRIBE channel [channel ...]
- 取消订阅
UNSUBSCRIBE [channel [channel ...]]
- 发布
publish channel message
- 查看通道订阅数
PUBSUB NUMSUB [channel [channel ...]]
源码分析
pubsub_channels
定义的属性是一个字典类型,保存着客户端和channel信息,key 值保存的就是频道名,value 是一个链表,链表中保存的是客户端 id。
- 结构体定义:
// pubsub_channels
struct redisServer {
//...
/* Pubsub */
dict *pubsub_channels; /* Map channels to list of subscribed clients */
dict *pubsub_patterns; /* A dict of pubsub_patterns */
int notify_keyspace_events; /* Events to propagate via Pub/Sub. This is an
xor of NOTIFY_... flags. */
dict *pubsubshard_channels; /* Map shard channels to list of subscribed clients */
//...
};
- 订阅源码分析:
// redis\src\pubsub.c
/* Subscribe a client to a channel. Returns 1 if the operation succeeded, or
* 0 if the client was already subscribed to that channel. */
int pubsubSubscribeChannel(client *c, robj *channel, pubsubtype type) {
dictEntry *de;
list *clients = NULL;
int retval = 0;
/* Add the channel to the client -> channels hash table */
// 添加频道至字典
if (dictAdd(type.clientPubSubChannels(c),channel,NULL) == DICT_OK) {
retval = 1;
incrRefCount(channel);
/* Add the client to the channel -> list of clients hash table */
// 添加客户端至链表
de = dictFind(*type.serverPubSubChannels, channel);
if (de == NULL) {
// 如果找不到,创建并添加
clients = listCreate();
dictAdd(*type.serverPubSubChannels, channel, clients);
incrRefCount(channel);
} else {
// 找得到,获取链表
clients = dictGetVal(de);
}
// 将客户端添加至链表尾部
listAddNodeTail(clients,c);
}
/* Notify the client */
// 客户端通知
addReplyPubsubSubscribed(c,channel,type);
return retval;
}
- 取消订阅源码分析:
// redis\src\pubsub.c
/* Unsubscribe a client from a channel. Returns 1 if the operation succeeded, or
* 0 if the client was not subscribed to the specified channel. */
// 1:成功 0:失败
int pubsubUnsubscribeChannel(client *c, robj *channel, int notify, pubsubtype type) {
dictEntry *de;
list *clients;
listNode *ln;
int retval = 0;
/* Remove the channel from the client -> channels hash table */
incrRefCount(channel); /* channel may be just a pointer to the same object
we have in the hash tables. Protect it... */
if (dictDelete(type.clientPubSubChannels(c),channel) == DICT_OK) {
retval = 1;
/* Remove the client from the channel -> clients list hash table */
// 从频道中删除客户端
de = dictFind(*type.serverPubSubChannels, channel);
serverAssertWithInfo(c,NULL,de != NULL);
// 获取客户端链表
clients = dictGetVal(de);
// 查找链表中对应节点
ln = listSearchKey(clients,c);
serverAssertWithInfo(c,NULL,ln != NULL);
listDelNode(clients,ln);
if (listLength(clients) == 0) {
/* Free the list and associated hash entry at all if this was
* the latest client, so that it will be possible to abuse
* Redis PUBSUB creating millions of channels. */
// 如果该频道下没有客户端了,在字典中删除该频道节点
dictDelete(*type.serverPubSubChannels, channel);
/* As this channel isn't subscribed by anyone, it's safe
* to remove the channel from the slot. */
if (server.cluster_enabled & type.shard) {
slotToChannelDel(channel->ptr);
}
}
}
/* Notify the client */
// 通知客户端
if (notify) {
addReplyPubsubUnsubscribed(c,channel,type);
}
decrRefCount(channel); /* it is finally safe to release it */
return retval;
}
基于模式
命令
- 订阅
# 订阅一个或多个符合给定模式的频道
PSUBSCRIBE pattern [pattern ...]
- 取消订阅
# 取消模式订阅
PUNSUBSCRIBE [pattern [pattern ...]]
- 获取订阅模式数量
# 返回订阅模式的数量,客户端订阅的所有模式的数量总和。时间复杂度 O(1)
PUBSUB NUMPAT
示例
订阅端
# 订阅端
127.0.0.1:6379> PSUBSCRIBE test.*
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "test.*"
3) (integer) 1
1) "pmessage"
2) "test.*"
3) "test.list"
4) "hello redis"
1) "pmessage"
2) "test.*"
3) "test.set"
4) "hello go"
发布端
# 发布端
127.0.0.1:6379> PUBLISH test.list "hello redis"
(integer) 1
127.0.0.1:6379> PUBLISH test.set "hello go"
(integer) 1
127.0.0.1:6379>
取消订阅
127.0.0.1:6379> PUNSUBSCRIBE test.*
1) "punsubscribe"
2) "test.*"
3) (integer) 1
源码分析
如果有某个/某些模式和该频道匹配,所有订阅这个/这些频道的客户端也同样会收到信息。
- 结构体:
// pubsub_patterns
struct redisServer {
//...
/* Pubsub */
dict *pubsub_channels; /* Map channels to list of subscribed clients */
dict *pubsub_patterns; /* A dict of pubsub_patterns */
int notify_keyspace_events; /* Events to propagate via Pub/Sub. This is an
xor of NOTIFY_... flags. */
dict *pubsubshard_channels; /* Map shard channels to list of subscribed clients */
//...
};
- 订阅源码分析:
// redis\src\pubsub.c
/* Subscribe a client to a pattern. Returns 1 if the operation succeeded, or 0 if the client was already subscribed to that pattern. */
// 1:成功 0:客户端已订阅
int pubsubSubscribePattern(client *c, robj *pattern) {
dictEntry *de;
list *clients;
int retval = 0;
if (listSearchKey(c->pubsub_patterns,pattern) == NULL) {
retval = 1;
// 新增pattern添加至链表尾部
listAddNodeTail(c->pubsub_patterns,pattern);
incrRefCount(pattern);
/* Add the client to the pattern -> list of clients hash table */
de = dictFind(server.pubsub_patterns,pattern);
if (de == NULL) {
clients = listCreate();
dictAdd(server.pubsub_patterns,pattern,clients);
incrRefCount(pattern);
} else {
clients = dictGetVal(de);
}
listAddNodeTail(clients,c);
}
/* Notify the client */
addReplyPubsubPatSubscribed(c,pattern);
return retval;
}
- 取消订阅源码分析:
// redis\src\pubsub.c
/* Unsubscribe a client from a channel. Returns 1 if the operation succeeded, or
* 0 if the client was not subscribed to the specified channel. */
int pubsubUnsubscribePattern(client *c, robj *pattern, int notify) {
dictEntry *de;
list *clients;
listNode *ln;
int retval = 0;
incrRefCount(pattern); /* Protect the object. May be the same we remove */
if ((ln = listSearchKey(c->pubsub_patterns,pattern)) != NULL) {
retval = 1;
listDelNode(c->pubsub_patterns,ln);
/* Remove the client from the pattern -> clients list hash table */
// 删除该模式下的客户端
de = dictFind(server.pubsub_patterns,pattern);
serverAssertWithInfo(c,NULL,de != NULL);
clients = dictGetVal(de);
ln = listSearchKey(clients,c);
serverAssertWithInfo(c,NULL,ln != NULL);
listDelNode(clients,ln);
if (listLength(clients) == 0) {
/* Free the list and associated hash entry at all if this was
* the latest client. */
// 如果没有客户端,则完全释放列表和关联的哈希条目
dictDelete(server.pubsub_patterns,pattern);
}
}
/* Notify the client */
if (notify) addReplyPubsubPatUnsubscribed(c,pattern);
decrRefCount(pattern);
return retval;
}