环境说明:redis源码版本 5.0.3;我在阅读源码过程做了注释,git地址:https://gitee.com/xiaoangg/redis_annotation 如有错误欢迎指正
参考书籍:《redis的设计与实现》
目录
一 频道的订阅与退订
1.1订阅频道
1.2 取消订阅
二 模式的订阅与退订
2.1订约模式
2.2取消订约模式
三 发送消息
四 查看订阅信息
Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息。
Redis 客户端通过subscribe可以订阅任意数量的频道;
发布与订阅的命令:
- PSUBSCRIBE pattern [pattern ...] 订阅一个或多个符合给定模式的频道。
- SUBSCRIBE channel [channel ...] 订阅给定的一个或多个频道的信息。
- UNSUBSCRIBE channel [channel ...] 命令用于退订给定的一个或多个频道的信息。
- PSUBSCRIBE pattern [pattern ...] 订阅一个或多个符合给定模式的频道。
- PUNSUBSCRIBE [pattern [pattern ...]] 退订所有给定模式的频道。
- PUBSUB <subcommand> [argument [argument ...]] 查看订阅与发布系统状态,它由数个不同格式的子命令组成。
一 频道的订阅与退订
当一个客户端执行SUBSCRIIBE命令时,客户端和chanel之间就建立了订阅关系;
client结构体中哈希表pubsub_channels属性记录 当前客户端订阅的所有channel;
server服务端状态哈希表pubsub_channels属性 记录了订阅channel的所有客户端信息;
server.h/redisServer
struct redisServer {
//.............
/* 订约发布相关 */
/* Pubsub */
dict *pubsub_channels; /* Map channels to list of subscribed clients */ //记录订约每个channel的客户端信息
//...............
}
server.h/client
typedef struct client {
//.........
dict *pubsub_channels; /* channels a client is interested in (SUBSCRIBE) */ //
/*发布/订约相关信息, clien订约的channel信息,SUBSCRIBE */
list *pubsub_patterns; /* patterns a client is interested in (SUBSCRIBE) */
sds peerid; /* Cached peer ID. */
//.............
} client;
1.1订阅频道
客户单执行SUBSCRITEB命令订阅某个频道;
服务端收到命令后将执行以下操作:
- 将channel信息添加到clien的pubsub_channels哈希表中,如果channel已经在pubsub_channels表中,不会重复添加;
- 将客户端添加到 服务端状态server 的pubsub_channels哈希表中; 同样 如果channel已经在pubsub_channels表中,不会重复添加;
SUBSCRITEB命令的实现代码位于 pubsub.c/pubsubSubscribeChannel()
/**
* 为客户机订阅频道。
* 操作成功,则返回1;
* 如果客户端已订阅该通道,则返回0
*/
/* 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) {
dictEntry *de;
list *clients = NULL;
int retval = 0;
/**
* 将channel记录到client.pubsub_channels 哈希表
* 注意 dictAdd函数,如果channel已经存在pubsub_channels哈希表中,将不会重复添加
*/
/* Add the channel to the client -> channels hash table */
if (dictAdd(c->pubsub_channels,channel,NULL) == DICT_OK) {
retval = 1;
incrRefCount(channel);
//订约这个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 */
addReply(c,shared.mbulkhdr[3]);
addReply(c,shared.subscribebulk);
addReplyBulk(c,channel);
addReplyLongLong(c,clientSubscriptionsCount(c));
return retval;
}
1.2 取消订阅
客户单执行UNSUBSCRITEB命令取消订阅某个频道;
操作正好的SUBSCRITEB命令的操作相反:
- 将channel信息 从clien的pubsub_channels哈希表中删除;
- 将客户端 从 服务端状态server 的pubsub_channels哈希表中删除;
UNSUBSCRITEB命令的实现位于 pubsub.c/unsubscribeCommand,
删除单个channel和删除所有channel的实现都是调用的pubsub.c/pubsubUnsubscribeChannel():
/**
* 从频道取消订阅客户端。如果操作成功,则返回1;如果客户端未订阅指定通道,则返回0。
*/
/* 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 pubsubUnsubscribeChannel(client *c, robj *channel, int notify) {
dictEntry *de;
list *clients;
listNode *ln;
int retval = 0;
// 从client->pubsub_channels 哈希表中移除从channel
/* Remove the channel from the client -> channels hash table */
//TODO 理解引用计数为啥要+1; channel可能只是指向哈希表中相同对象的指针。引用计数+1保护它
incrRefCount(channel); /* channel may be just a pointer to the same object
we have in the hash tables. Protect it... */
//dictDelete 从client->pubsub_channels 哈希表中移除从channel
if (dictDelete(c->pubsub_channels,channel) == DICT_OK) {
retval = 1;
//从server->pubsub_channels哈希表中移除channel
/* Remove the client from the channel -> clients list hash table */
de = dictFind(server.pubsub_channels,channel);
serverAssertWithInfo(c,NULL,de != NULL);
clients = dictGetVal(de);
ln = listSearchKey(clients,c);
serverAssertWithInfo(c,NULL,ln != NULL);
listDelNode(clients,ln);
//如何channel已经没有clien订阅他了,释放server.pubsub_channels的channel
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(server.pubsub_channels,channel);
}
}
/* Notify the client */
if (notify) {
addReply(c,shared.mbulkhdr[3]);
addReply(c,shared.unsubscribebulk);
addReplyBulk(c,channel);
addReplyLongLong(c,dictSize(c->pubsub_channels)+
listLength(c->pubsub_patterns));
}
//TODO 这个应该是和上面的incrRefCount呼应,理解这两块
decrRefCount(channel); /* it is finally safe to release it */
return retval;
}
二 模式的订阅与退订
除了SUBSCRIBE和UNSCRIBE命令,redis还支持GLOB风格的订阅命令;
和订阅类似
client结构体中列表pubsub_patterns属性记录 当前客户端订阅的所有channel;
server服务端状态中列表pubsub_patterns属性 记录所有的订阅模式,pubsub_patterns是个server.h/pubsubPattern结构体列表;
server.h/redisServer
struct redisServer {
//.............
/* 订约发布相关 */
/* Pubsub */
list *pubsub_patterns; /* A list of pubsub_patterns */ //所有 模式订阅 的列表
//...............
}
server.h/client
typedef struct client {
//.........
list *pubsub_patterns; /* patterns a client is interested in (SUBSCRIBE) */
/*发布/订约, client 模式订约的channel信息,SUBSCRIBE */
//.............
} client;
server.h/pubsubPattern
/**
* 模式订阅
* server->pubsub_patterns属性就是pubsubPattern列表
*/
typedef struct pubsubPattern {
client *client; //订阅的客户端
robj *pattern; //模式信息
} pubsubPattern;
2.1订约模式
客户端执行PSUBSCRITEB命令订阅某个模式;
服务端收到命令后执行以下操作:
- 将参数中的pattern添加到客户端的client->pubsub_patterns列表属性中;
- 将pubsubPattern类型的订阅模式 添加到server->pubsub_patterns列表中;
PSUBSCRITEB命令的实现入口pubsub.c/pubsubSubscribePattern()
/**
* 客户端模式订阅
*/
/** Subscribe a client to a pattern.
* Returns 1 if the operation succeeded,
* or 0 if the client was already subscribed to that pattern.
*/
int pubsubSubscribePattern(client *c, robj *pattern) {
int retval = 0;
//client没有订阅pattern
if (listSearchKey(c->pubsub_patterns,pattern) == NULL) {
retval = 1;
pubsubPattern *pat;
//pattern添加到client没有订阅pattern->pubsub_patterns列表中
listAddNodeTail(c->pubsub_patterns,pattern);
incrRefCount(pattern);
pat = zmalloc(sizeof(*pat));
pat->pattern = getDecodedObject(pattern);
pat->client = c;
//将pubsubPattern pat添加到server->pubsub_patterns列表中
listAddNodeTail(server.pubsub_patterns,pat);
}
/* Notify the client */
addReply(c,shared.mbulkhdr[3]);
addReply(c,shared.psubscribebulk);
addReplyBulk(c,pattern);
addReplyLongLong(c,clientSubscriptionsCount(c));
return retval;
}
2.2取消订约模式
同样取消和 订阅 操作正好相反,PUNSUBSCRIPT命令的实现位于 pubsub.c/pubsubUnsubscribePattern()
/**
* 取消订阅模式;
* 操作成功返回1,失败返回0;
*/
/* 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) {
listNode *ln;
pubsubPattern pat;
int retval = 0;
incrRefCount(pattern); /* Protect the object. May be the same we remove */
//从客户端client->pubsub_patterns 查找pattern
if ((ln = listSearchKey(c->pubsub_patterns,pattern)) != NULL) {
retval = 1;
//从client->pubsub_patterns 列表中删除pubsub_patterns
listDelNode(c->pubsub_patterns,ln);
pat.client = c;
pat.pattern = pattern;
//从server->pubsub_patterns 列表中删除pat
ln = listSearchKey(server.pubsub_patterns,&pat);
listDelNode(server.pubsub_patterns,ln);
}
/* Notify the client */
if (notify) {
addReply(c,shared.mbulkhdr[3]);
addReply(c,shared.punsubscribebulk);
addReplyBulk(c,pattern);
addReplyLongLong(c,dictSize(c->pubsub_channels)+
listLength(c->pubsub_patterns));
}
decrRefCount(pattern);
return retval;
}
三 发送消息
当有客户端执行PUBLISH <channel> <msg> ,将msg发送给channel时,服务端执行以下两个操作:
- 将消息发送给channel订阅者
- 将消息发送给模式订阅者
代码实现的入口:pubsub.c/pubsubPublishMessage
/**
* 推送一条消息
*/
/* Publish a message */
int pubsubPublishMessage(robj *channel, robj *message) {
int receivers = 0;
dictEntry *de;
listNode *ln;
listIter li;
/* 从server.pubsub_channels查找channel。没有找到说明没有客户端订阅该channel */
/* 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);
//遍历channel的订阅者发送消息
while ((ln = listNext(&li)) != NULL) {
client *c = ln->value;
addReply(c,shared.mbulkhdr[3]);
addReply(c,shared.messagebulk);
addReplyBulk(c,channel);
addReplyBulk(c,message);
receivers++;
}
}
/* 遍历模式订阅者 发送消息*/
/* Send to clients listening to matching channels */
if (listLength(server.pubsub_patterns)) {
listRewind(server.pubsub_patterns,&li);
channel = getDecodedObject(channel);
//遍历模式订阅,匹配成功发送消息
while ((ln = listNext(&li)) != NULL) {
pubsubPattern *pat = ln->value;
if (stringmatchlen((char*)pat->pattern->ptr,
sdslen(pat->pattern->ptr),
(char*)channel->ptr,
sdslen(channel->ptr),0)) {
addReply(pat->client,shared.mbulkhdr[4]);
addReply(pat->client,shared.pmessagebulk);
addReplyBulk(pat->client,pat->pattern);
addReplyBulk(pat->client,channel);
addReplyBulk(pat->client,message);
receivers++;
}
}
decrRefCount(channel);
}
return receivers;
}
四 查看订阅信息
redis可以通过PUBSUB subcommand命令查看频道或者模式相关信息;
PUBSUB的实现入口位于pubsub.c/pubsubCommand;
可以通过代码看到pubsub命令支持以下子命令:
- PUBSUB help 查帮助
- PUBSUB CHANNELS [<pattern>] 获取当前服务器所有被订阅的channel,可选参数pattern:和pattern匹配的 遍历server->pubsub_channels;时间复杂度O(n)
- PUBSUB NUMSUB [Channel_1 ... Channel_N] 返回给定各个channel的订约这数量 遍历server->pubsub_channels;时间复杂度O(k) 传入参数的数量
- PUBSUB NUMPAT 返回服务器当前被订约的模式的数量 直接返回server->pubsub_patterns 列表长度 ;时间复杂度O(1)
/**
*/
/* PUBSUB command for Pub/Sub introspection. */
void pubsubCommand(client *c) {
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
const char *help[] = {
"CHANNELS [<pattern>] -- Return the currently active channels matching a pattern (default: all).",
"NUMPAT -- Return number of subscriptions to patterns.",
"NUMSUB [channel-1 .. channel-N] -- Returns the number of subscribers for the specified channels (excluding patterns, default: none).",
NULL
};
addReplyHelp(c, help);
} else if (!strcasecmp(c->argv[1]->ptr,"channels") &&
(c->argc == 2 || c->argc == 3))
{ //获取当前服务器被订约的channel,可选参数pattern:返回被订约的与pattern模式匹配的频道
/* PUBSUB CHANNELS [<pattern>] */
sds pat = (c->argc == 2) ? NULL : c->argv[2]->ptr;
dictIterator *di = dictGetIterator(server.pubsub_channels);
dictEntry *de;
long mblen = 0;
void *replylen;
replylen = addDeferredMultiBulkLength(c);
//遍历server.pubsub_channels
while((de = dictNext(di)) != NULL) {
robj *cobj = dictGetKey(de);
sds channel = cobj->ptr;
//匹配了pattern可选参数的channel
if (!pat || stringmatchlen(pat, sdslen(pat),
channel, sdslen(channel),0))
{
addReplyBulk(c,cobj);
mblen++;
}
}
dictReleaseIterator(di);
setDeferredMultiBulkLength(c,replylen,mblen);
} else if (!strcasecmp(c->argv[1]->ptr,"numsub") && c->argc >= 2) {
// 返回给定 各个chanenl 订约者的数量
/* PUBSUB NUMSUB [Channel_1 ... Channel_N] */
int j;
addReplyMultiBulkLen(c,(c->argc-2)*2);
for (j = 2; j < c->argc; j++) {
list *l = dictFetchValue(server.pubsub_channels,c->argv[j]);
addReplyBulk(c,c->argv[j]);
addReplyLongLong(c,l ? listLength(l) : 0);
}
} else if (!strcasecmp(c->argv[1]->ptr,"numpat") && c->argc == 2) {
//返回服务器当前被订约的模式的数量
/* PUBSUB NUMPAT */
addReplyLongLong(c,listLength(server.pubsub_patterns));
} else {
addReplySubcommandSyntaxError(c);
}
}