结合redis设计与实现的redis源码学习-17-发布与订阅(pubsub.c)
2017-11-30 23:02
806 查看
Redis的发布预定月功能由PUBLISH,SUBSCRIBE,PSUBSCRIBE等命令组成。通过执行SUBSCRIBE命令,客户端可以订阅一个或者多个频道,成为这个频道的订阅者:每当有其他客户端向被订阅的频道发送消息是,该频道的所有订阅者都会收到这条消息。
客户端还可以通过执行PSUBSCRIBE来订阅一个或者多个模式,从而成为这些模式的订阅者:每当有其他客户端向某个频道发送消息时,消息不仅会被发送给这个频道的所有订阅者,它还会被发送给所有与这个频道相匹配的模式的订阅者。
当客户端执行订阅命令时,服务器会将客户单与被订阅的频道进行关联,如果频道已经有订阅者了,会将客户端添加到键所对应的链表末尾;如果没有这个键,那么就创建该键,然后再讲这个客户端添加到链表。
每当客户端执行模式订阅命令时,服务器会对每个被订阅的模式执行两个操作:
-1、新建一个pubsubPattern结构,将结构的pattern属性设置为被订阅的模式,client属性设置为订阅模式的客户端。
-2、将pubsubPattern尾插到链表末尾。
每当客户端退订模式时,服务器将在链表中查找并杀出那些模式属性为被退订模式与客户端的节点。
发送给频道订阅者
服务器状态中的pubsub_channels字典记录了所有频道的订阅关系,所以为了将消息发送给该频道的所有订阅者,发布命令要做的就是在字典里寻找频道的订阅者名单,然后将消息发送给名单上所有的客户端。
发送给模式订阅者
服务器状态中的pubsub_patterns记录了所有模式的订阅关系,所以为了将消息发送给所有与频道相匹配的模式的订阅者,发布命令要遍历整个链表,查找那些与频道相匹配的模式,并将消息发送给订阅了这些模式的客户端。
PUBSUB CHANNELS
该命令用于反悔服务器当前被订阅的频道,这个命令通过遍历服务器的订阅字典来完成的。
PUBSUB NUMBER
这个命令可以接受任意多个频道作为输入,返回这些频道的订阅者数量。
它是通过在订阅字典中吵到对应的订阅者链表,然后返回链表长度实现的。
PUBSUB NUMPAT
这个命令用于返回服务器当前被订阅模式的数量。它是通过返回模式链表的长度来实现的。
下面是命令执行调用的函数:
客户端还可以通过执行PSUBSCRIBE来订阅一个或者多个模式,从而成为这些模式的订阅者:每当有其他客户端向某个频道发送消息时,消息不仅会被发送给这个频道的所有订阅者,它还会被发送给所有与这个频道相匹配的模式的订阅者。
频道订阅
Redis将所有频道的订阅关系都保存在服务器状态的pubsub_channels字典里面,这个字典的键是某个被订阅的频道,而键的值则是一个链表,链表里面记录了所有订阅这个频道的客户端。当客户端执行订阅命令时,服务器会将客户单与被订阅的频道进行关联,如果频道已经有订阅者了,会将客户端添加到键所对应的链表末尾;如果没有这个键,那么就创建该键,然后再讲这个客户端添加到链表。
频道退订
客户端执行退订命令时,程序会根据被退订频道的名字,在字典中找到对应的链表,然后从链表中删除这个客户端的信息。如果删除之后这个链表为空,那么就在字典中删除该键。模式的订阅与退订
与订阅类似,Redis将所有模式的订阅保存在pubsub_pattern链表中,每个节点包含一个pubsubPattern结构:typedef struct pubsubPattern { client *client;//订阅模式的客户端 robj *pattern;//被订阅的模式 } pubsubPattern;
每当客户端执行模式订阅命令时,服务器会对每个被订阅的模式执行两个操作:
-1、新建一个pubsubPattern结构,将结构的pattern属性设置为被订阅的模式,client属性设置为订阅模式的客户端。
-2、将pubsubPattern尾插到链表末尾。
每当客户端退订模式时,服务器将在链表中查找并杀出那些模式属性为被退订模式与客户端的节点。
发送消息
当一个客户端执行publish命令时,服务器会将消息发送给对应频道的所有订阅者,如果有一个或者多个模式与频道相匹配,那么将消息发送给模式的订阅者。发送给频道订阅者
服务器状态中的pubsub_channels字典记录了所有频道的订阅关系,所以为了将消息发送给该频道的所有订阅者,发布命令要做的就是在字典里寻找频道的订阅者名单,然后将消息发送给名单上所有的客户端。
发送给模式订阅者
服务器状态中的pubsub_patterns记录了所有模式的订阅关系,所以为了将消息发送给所有与频道相匹配的模式的订阅者,发布命令要遍历整个链表,查找那些与频道相匹配的模式,并将消息发送给订阅了这些模式的客户端。
查看订阅信息
客户端可以通过pubsub命令来查看频道或者模式的相关信息,比如某个频道目前有多少个订阅者,又或者某个模式目前有多少订阅者。PUBSUB CHANNELS
该命令用于反悔服务器当前被订阅的频道,这个命令通过遍历服务器的订阅字典来完成的。
PUBSUB NUMBER
这个命令可以接受任意多个频道作为输入,返回这些频道的订阅者数量。
它是通过在订阅字典中吵到对应的订阅者链表,然后返回链表长度实现的。
PUBSUB NUMPAT
这个命令用于返回服务器当前被订阅模式的数量。它是通过返回模式链表的长度来实现的。
看代码:Pubsub.c
#include "server.h" /*----------------------------------------------------------------------------- * Pubsub low level API *----------------------------------------------------------------------------*/ //释放模式结构体 void freePubsubPattern(void *p) { pubsubPattern *pat = p; decrRefCount(pat->pattern); zfree(pat); } //对比模式是否相等 int listMatchPubsubPattern(void *a, void *b) { pubsubPattern *pa = a, *pb = b; return (pa->client == pb->client) && (equalStringObjects(pa->pattern,pb->pattern)); } /* Return the number of channels + patterns a client is subscribed to. 返回客户端订阅的所有模式数量和频道数量*/ int clientSubscriptionsCount(client *c) { return dictSize(c->pubsub_channels)+ listLength(c->pubsub_patterns); } /* Subscribe a client to a channel. Returns 1 if the operation succeeded, or 0 if the client was already subscribed to that channel. 订阅一个频道,返回0是重复订阅了*/ 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 通知客户端*/ addReply(c,shared.mbulkhdr[3]); addReply(c,shared.subscribebulk); addReplyBulk(c,channel); addReplyLongLong(c,clientSubscriptionsCount(c)); return retval; } /* 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; /* 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(c->pubsub_channels,channel) == DICT_OK) { retval = 1; /* 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); 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)); } decrRefCount(channel); /* it is finally safe to release it 所有操作完成,可以安全释放*/ return retval; } /* Subscribe a client to a pattern. Returns 1 if the operation succeeded, or 0 if the client was already subscribed to that pattern. 订阅一个模式,返回0代表重复订阅*/ int pubsubSubscribePattern(client *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,clientSubscriptionsCount(c)); return retval; } /* Unsubscribe a client from a channel. Returns 1 if the operation succeeded, or 0 if the client was not subscribed to the specified channel. 取消订阅模式,0没有订阅指定模式*/ 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 保护模式对象,防止释放*/ if ((ln = listSearchKey(c->pubsub_patterns,pattern)) != NULL) {//查找这个模式 retval = 1; listDelNode(c->pubsub_patterns,ln); pat.client = c; pat.pattern = pattern; 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; } /* Unsubscribe from all the channels. Return the number of channels the client was subscribed to. 退订所有频道,返回订阅的频道数目*/ int pubsubUnsubscribeAllChannels(client *c, int notify) { dictIterator *di = dictGetSafeIterator(c->pubsub_channels); dictEntry *de; int count = 0; //遍历订阅字典 while((de = dictNext(di)) != NULL) { robj *channel = dictGetKey(de); count += pubsubUnsubscribeChannel(c,channel,notify);//取消订阅频道 } /* We were subscribed to nothing? Still reply to the client. 没有取消掉任何订阅,返回客户端*/ if (notify && count == 0) { addReply(c,shared.mbulkhdr[3]); addReply(c,shared.unsubscribebulk); addReply(c,shared.nullbulk); addReplyLongLong(c,dictSize(c->pubsub_channels)+ listLength(c->pubsub_patterns)); } dictReleaseIterator(di); return count; } /* Unsubscribe from all the patterns. Return the number of patterns the client was subsc c861 ribed from. 退订所有模式*/ int pubsubUnsubscribeAllPatterns(client *c, int notify) { listNode *ln; listIter li; int count = 0; listRewind(c->pubsub_patterns,&li); while ((ln = listNext(&li)) != NULL) {//遍历客户端模式订阅链表 robj *pattern = ln->value; count += pubsubUnsubscribePattern(c,pattern,notify);//取消订阅模式 } if (notify && count == 0) { /* We were subscribed to nothing? Still reply to the client. */ addReply(c,shared.mbulkhdr[3]); addReply(c,shared.punsubscribebulk); addReply(c,shared.nullbulk); addReplyLongLong(c,dictSize(c->pubsub_channels)+ listLength(c->pubsub_patterns)); } return count; } /* Publish a message 发布一条消息*/ int pubsubPublishMessage(robj *channel, robj *message) { int receivers = 0; dictEntry *de; 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; 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; }
下面是命令执行调用的函数:
//订阅命令 void subscribeCommand(client *c) { int j; //遍历所有命令后的参数,订阅频道 for (j = 1; j < c->argc; j++) pubsubSubscribeChannel(c,c->argv[j]); c->flags |= CLIENT_PUBSUB; } //退订频道 void unsubscribeCommand(client *c) { if (c->argc == 1) {//退订所有频道 pubsubUnsubscribeAllChannels(c,1); } else { int j; for (j = 1; j < c->argc; j++)//遍历所有要退订的频道 pubsubUnsubscribeChannel(c,c->argv[j],1); } if (clientSubscriptionsCount(c) == 0) c->flags &= ~CLIENT_PUBSUB;//客户端置为没有订阅的状态 } //订阅模式命令 void psubscribeCommand(client *c) { int j; //订阅所有模式 for (j = 1; j < c->argc; j++) pubsubSubscribePattern(c,c->argv[j]); c->flags |= CLIENT_PUBSUB; } //取消订阅模式 void punsubscribeCommand(client *c) { if (c->argc == 1) { pubsubUnsubscribeAllPatterns(c,1); } else { int j; for (j = 1; j < c->argc; j++) pubsubUnsubscribePattern(c,c->argv[j],1); } if (clientSubscriptionsCount(c) == 0) c->flags &= ~CLIENT_PUBSUB; } //发布命令 void publishCommand(client *c) { int receivers = pubsubPublishMessage(c->argv[1],c->argv[2]); if (server.cluster_enabled)//如果在集群中 clusterPropagatePublish(c->argv[1],c->argv[2]);//通知所有发布 else forceCommandPropagation(c,PROPAGATE_REPL);//通知复制 addReplyLongLong(c,receivers); } /* PUBSUB command for Pub/Sub introspection. pubsub命令*/ void pubsubCommand(client *c) { if (!strcasecmp(c->argv[1]->ptr,"channels") && (c->argc == 2 || c->argc ==3)) { /* 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); while((de = dictNext(di)) != NULL) {//遍历所有订阅模式 robj *cobj = dictGetKey(de); sds channel = cobj->ptr; 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) { /* 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 { addReplyErrorFormat(c, "Unknown PUBSUB subcommand or wrong number of arguments for '%s'", (char*)c->argv[1]->ptr); } }
相关文章推荐
- 结合redis设计与实现的redis源码学习-8.3-t_list.c(列表键)
- 结合redis设计与实现的redis源码学习-11.1-命令的实现(Db.c)
- 结合redis设计与实现的redis源码学习-8.1-object.c(对象实现)
- 结合redis设计与实现的redis源码学习-21-哨兵(Sentinel.c)
- redis 学习手册之发布和订阅pubsub操作
- 结合redis设计与实现的redis源码学习-3-链表
- 结合redis设计与实现的redis源码学习-14-事件(ae.c/ae_epoll.c)
- 结合redis设计与实现的redis源码学习-15-TCP网络连接(anet.c)
- Redis学习笔记(九) 命令进阶:Pub/Sub(发布/订阅)操作
- 结合redis设计与实现的redis源码学习-20-复制(replication.c)
- .Net Redis实现发布/订阅(RedisPubSubServer)
- 结合redis设计与实现的redis源码学习-9-zipmap(压缩图)
- 结合redis设计与实现的redis源码学习-23-排序(sort.c)
- 结合redis设计与实现的redis源码学习-11-数据库(server.h/redisDb,notify.c)
- Redis源码分析(三十)--- pubsub发布订阅模式
- 结合redis设计与实现的redis源码学习-10-hyperloglog(基数统计)
- linux下使用hiredis异步API实现sub/pub消息订阅和发布的功能 标签: hiredishiredis异步APIhiredis事件处理redis消息订阅发布redis c接口 2016-
- 结合redis设计与实现的redis源码学习-4-dict(字典)
- redis源码分析之发布订阅(pub/sub)
- 结合redis设计与实现的redis源码学习-5-skiplist(跳跃表)