实现原理
有兴趣看Redis pub/sub源码的可以移步这里,有中文注释的哦,感谢作者的奉献。
订阅(Subscribe)
先直接上源码
/*
* 为客户端订阅指定的频道
*
* 订阅成功返回 1 ,如果频道已经订阅,返回 0 。
*
* T = O(1)
*/
int pubsubSubscribeChannel(redisClient *c, robj *channel) {
struct dictEntry *de;
list *clients = NULL;
int retval = 0;
/* Add the channel to the client -> channels hash table */
// 将 channel 添加到客户端的 pubsub_channels 字典中
// 键为 channel ,值为 NULL
// O(1)
if (dictAdd(c->pubsub_channels,channel,NULL) == DICT_OK) {
retval = 1;
incrRefCount(channel);
/* Add the client to the channel -> list of clients hash table */
// 将客户端添加到频道的订阅链表里
// 其中 server.pubsub_channels 为字典
// 字典的键为 channel ,值为链表,链表里保存了订阅该频道的所有客户端
// O(1)
de = dictFind(server.pubsub_channels,channel);
if (de == NULL) {
clients = listCreate();
// O(1)
dictAdd(server.pubsub_channels,channel,clients);
incrRefCount(channel);
} else {
clients = dictGetVal(de);
}
// O(1)
listAddNodeTail(clients,c);
}
/* Notify the client */
// 向客户端返回值,告知订阅已成功
addReply(c,shared.mbulkhdr[3]);
addReply(c,shared.subscribebulk);
addReplyBulk(c,channel);
addReplyLongLong(c,dictSize(c->pubsub_channels)+listLength(c->pubsub_patterns));
return retval;
}
主要做了两件事情
-
判断新的channel是否存在于客户端的pubsub_channels中,如果不存在执行下面步骤
-
将channel添加到客户端的pubsub_channels的哈希表中。
-
将客户端添加到server.pubsub_channels字典中channel的链表中。
模式订阅
/* Subscribe a client to a pattern. Returns 1 if the operation succeeded, or 0 if the clinet was already subscribed to that pattern. */
/*
* 订阅一个模式
*
* 订阅成功返回 1 ,如果已经订阅返回 0 。
*
* T = O(N)
*/
int pubsubSubscribePattern(redisClient *c, robj *pattern) {
int retval = 0;
// 如果 pattern 未保存在 c->pubsub_patterns 链表,那么。。。
// O(N)
if (listSearchKey(c->pubsub_patterns,pattern) == NULL) {
retval = 1;
pubsubPattern *pat;
// 将模式加入客户端链表, O(1)
listAddNodeTail(c->pubsub_patterns,pattern);
incrRefCount(pattern);
// 将模式和客户端信息记录到服务器
// O(1)
pat = zmalloc(sizeof(*pat));
pat->pattern = getDecodedObject(pattern);
pat->client = c;
listAddNodeTail(server.pubsub_patterns,pat);
}
/* Notify the client */
addReply(c,shared.mbulkhdr[3]);
addReply(c,shared.psubscribebulk);
addReplyBulk(c,pattern);
addReplyLongLong(c,dictSize(c->pubsub_channels)+listLength(c->pubsub_patterns));
return retval;
}
也是做了两件事情
-
判断新的pattern是否存在于客户端的pubsub_patterns链表中,如果存在继续下面步骤
-
将新的pattern加入客户端的pubsub_patterns链表中,可以看到这里使用的是链表而不是哈希表,主要是因为发送消息的时候,pattern模式每次都需要遍历所有的项来匹配适合,所以不需要哈希表这种通过键值查找的数据结构
-
新建一个新的pattern,指向client,并添加到server.pubsub_patterns链表尾部,这里需要注意的是不管新的pattern是否在链表中存在,都是会新建一个对象插入到链表尾部。
退订/退订模式
退订的话就是相当于做了个逆向操作,将订阅的时候添加的东西进行删除。
发布
/* Publish a message */
int pubsubPublishMessage(robj *channel, robj *message) {
int receivers = 0;
struct dictEntry *de;
listNode *ln;
listIter li;
/* Send to clients listening for that channel */
// 取出所有订阅给定频道的客户端, O(1)
de = dictFind(server.pubsub_channels,channel);
if (de) {
list *list = dictGetVal(de);
listNode *ln;
listIter li;
// 将信息发送至至所有订阅者, O(N)
listRewind(list,&li);
while ((ln = listNext(&li)) != NULL) {
redisClient *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 */
// 匹配的数量不为 0
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;
}
-
取出所有订阅给定频道的客户端,将信息发送至至所有订阅者.
-
判断服务端模式的链表不为空,则遍历所有模式,并将信息发送给匹配的模式。
从这里也可以看出上篇文章所说的,订阅一个channel再订阅一个匹配这个channel的pattern,会多次发送消息。