Redis源码分析--发布与订阅
发布与订阅:
一、介绍:
Redis提供了一个轻量的订阅机制:
命令 | 功能 |
---|---|
PUBLISH channel message | 将消息发送到指定的频道 |
SUBSCRIBE channel [channel …] | 订阅给定的一个或多个频道的信息 |
UNSUBSCRIBE [channel [channel …]] | 退订给定的频道 |
PSUBSCRIBE pattern [pattern …] | 订阅一个或多个符合给定模式的频道 |
PUNSUBSCRIBE [pattern [pattern …]] | 退订所有给定模式的频道 |
下面将介绍SUBCRIBE、PSUBSCRIBE以及PUBLISH,退订实现与订阅相似,不赘述;
二、SUBSCRIBE:
为了实现订阅关系,redisServer和 redisClient都有对应的结构:
struct redisServer {
// ...
// 保存所有频道的订阅关系
dict *pubsub_channels;
// ...
};
struct redisClient {
// ...
dict *pubsub_channels;
// ...
};
- L4:保存所有频道的订阅关系,key:channel,val:list of clients;
- L10:保存该客户端订阅的频道,key:channel,val:NULL;
订阅命令的实现:
void subscribeCommand(redisClient *c) {
int j;
/* 命令:SUBSCRIBE channel [channel …],对所有channel参数进行subscribe*/
for (j = 1; j < c->argc; j++)
pubsubSubscribeChannel(c,c->argv[j]);
}
/* 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(redisClient *c, robj *channel) {
struct dictEntry *de;
list *clients = NULL;
int retval = 0;
/* Add the channel to the client -> channels hash table */
/*
* client端:保存所有本客户端订阅的频道
* pubsub_channels: {
* "chan1": NULL,
* "chan2": NULL,
* ...
* }
* server端:保存所有的订阅信息,key:channel,val:list of clients
* pubsub_channels: {
* "chan1": client3->client2,
* "chan2": client2->client1->client3,
* ...
* }
*/
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) {
/* 如果server保存的订阅信息找不到该频道,新建一个空client链表并插入channel对应的key中 */
clients = listCreate();
dictAdd(server.pubsub_channels,channel,clients);
incrRefCount(channel);
} else {
clients = dictGetVal(de);
}
/* 新客户端插到链表的尾部 */
listAddNodeTail(clients,c);
}
/* Notify the client */
/* 大概说一下,添加"*3\r\n",下标3就是数字3,redis使用这些共享的sds来减少内存消耗 */
addReply(c,shared.mbulkhdr[3]);
/* "$9\r\nsubscribe\r\n" */
addReply(c,shared.subscribebulk);
addReplyBulk(c,channel);
addReplyLongLong(c,dictSize(c->pubsub_channels)+listLength(c->pubsub_patterns));
return retval;
}
- L9~L21: redisServer和 redisClient结构体中都有 dict *pubsub_channels字典,但是保存的val不同;
- L40~L46:执行结果返回个客户端;
三、PSUBSCRIBE:
为了实现订阅关系,redisServer和 redisClient都有对应的结构:
typedef struct pubsubPattern {
redisClient *client;
robj *pattern;
} pubsubPattern;
struct redisServer {
// ...
list *pubsub_patterns;
// ...
};
struct redisClient {
// ...
list *pubsub_patterns;
// ...
};
- L8:保存所有模式以及订阅的频道,val:pubsubPattern;
- L14:保存该客户端订阅的频道,val:pattern字符串;
void psubscribeCommand(redisClient *c) {
int j;
/* 命令:PSUBSCRIBE pattern [pattern …] */
for (j = 1; j < c->argc; j++)
pubsubSubscribePattern(c,c->argv[j]);
}
/* 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(redisClient *c, robj *pattern) {
int retval = 0;
/* 如果找不到模式链表中对应的模式,新建并插入链表中 */
if (listSearchKey(c->pubsub_patterns,pattern) == NULL) {
retval = 1;
pubsubPattern *pat;
listAddNodeTail(c->pubsub_patterns,pattern);
incrRefCount(pattern);
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;
}
- L6:pattern必须在中模式链表找到完全匹配的模式;
四、PUBLISH:
void publishCommand(redisClient *c) {
int receivers = pubsubPublishMessage(c->argv[1],c->argv[2]);
/* publish发送的消息要和从服务器一致,所以进行命令传播 */
forceCommandPropagation(c,REDIS_PROPAGATE_REPL);
addReplyLongLong(c,receivers);
}
- L4:publish发送的消息要和从服务器一致,所以进行命令传播;
/* 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 */
de = dictFind(server.pubsub_channels,channel);
if (de) {
/* 保存了所有订阅channel的client的链表 */
list *list = dictGetVal(de);
listNode *ln;
listIter li;
/* 从list->head开始遍历,将消息发送给订阅的client */
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 */
/* 发送给模式匹配的订阅者 */
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;
}
五、键空间通知:
2.8版本开始,Redis引入了键空间通知(keyspace notifications),户端可以通过订阅/发布(Pub/Sub)机制,接收那些以某种方式改变了Redis数据空间的事件通知。具体看Redis键空间通知[1][2],具体实现也是用 pubsubPublishMessage来发布消息的。