redis实现的发送订阅系统,即pub-sub,这部分的的代码比较少,也方便分析。在这只将会分析下普通的pub-sub(会忽略掉Pattern-matching subscriptions),以此来简述一个pubsub系统是如何实现的。
在redis主要有介绍redis的pub-sub,在开始之前, 需要知道redis的pubsub的几个命令:
SUBSCRIBE first second //订阅两个channel,分别是first和second
PUBLISH secondHello //发送方向channel是second的发送"hello"消息
UNSUBSCRIBE //取消之前订阅的所有channel
下面来看看在redis的Pubsub.c代码中对这些命令的实现。
首先看看subscribe的实现:
voidsubscribeCommand(redisClient *c) { int j; for (j = 1; j < c->argc; j++) //客户端订阅多个channel,对于需要订阅的每个channel都执行该函数 pubsubSubscribeChannel(c,c->argv[j]); } /* Subscribe aclient to a channel. Returns 1 if the operation succeeded, or * 0 if the client was already subscribed tothat channel. */ intpubsubSubscribeChannel(redisClient *c, robj *channel) { struct dictEntry *de; list *clients = NULL; int retval = 0; //c->pubsub_channels是一个redis自己实现的hash表,这不是我们的重点,就不展开说了 //dictAdd向hash表中增加一个key为channel,value为null的键值对 /* Add the channel to the client ->channels hash table */ if(dictAdd(c->pubsub_channels,channel,NULL) == DICT_OK) { retval = 1; incrRefCount(channel); //从服务端查找当前需要订阅的channel是否已经在服务端登记过 //没有登记过,则创建一个列表作为channel的值,并记录到服务端 //也就是意味着服务端是通过这个列表来得知这个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); } //向这个channel所对应的记录客户端的列表的尾部插入当前客户端, //这样服务端就当前客户端订阅了这个channel 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; } //在看看pub的实现: voidpublishCommand(redisClient *c) { //发送则向某个channel发送消息,如 PUBLISH second Hello //c->args[1]就是channel second,第二个参数就是要发送的消息Hello int receivers =pubsubPublishMessage(c->argv[1],c->argv[2]); addReplyLongLong(c,receivers); } /*Publish a message */ intpubsubPublishMessage(robj *channel, robj *message) { int receivers = 0; struct dictEntry *de; listNode *ln; listIter li; //从服务端找到订阅了这个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); 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 */ 发送到模式匹配的订阅方的处理... return receivers; } //最后看看unsubscribe的处理: void unsubscribeCommand(redisClient *c) { if (c->argc == 1) { //取消这个客户端的所有订阅 pubsubUnsubscribeAllChannels(c,1); } else { int j; for (j = 1; j < c->argc; j++) //取消这个客户端的关于某个channel的订阅 pubsubUnsubscribeChannel(c,c->argv[j],1); } } /* Unsubscribe from all the channels. Return the number of channels the * client was subscribed from. */ int pubsubUnsubscribeAllChannels(redisClient *c, int notify) { //取得这个客户端的所有订阅的channel的迭代器 dictIterator *di =dictGetSafeIterator(c->pubsub_channels); dictEntry *de; int count = 0; //通过遍历迭代器来获取在客户端记录的每个channel记录,并对每个记录取消订阅 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 a client from a channel. Returns 1 if the operationsucceeded, or * 0 if the client was not subscribed to the specified channel. */ int pubsubUnsubscribeChannel(redisClient *c, robj *channel, int notify) { struct dictEntry *de; list *clients; listNode *ln; int retval = 0; //从客户端记录的hash表中删除这个channel记录,以此来取消客户对这个channel的订阅 //从服务端查找channel所对应客户端列表,从这个列表中删除这个客户端的记录 //这样就完成了取消订阅 /* Remove the channel from the client -> channels hashtable */ incrRefCount(channel); /* channel may be just a pointer tothe 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); redisAssertWithInfo(c,NULL,de != NULL); clients = dictGetVal(de); ln = listSearchKey(clients,c); redisAssertWithInfo(c,NULL,ln != NULL); listDelNode(clients,ln); if (listLength(clients) == 0) { /* Free the list and associatedhash entry at all if this was * the latest client, sothat it will be possible to abuse * Redis PUBSUB creatingmillions 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; }