redis-2.6.16源码分析之pub-sub系统

  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;

}


 

 

 

你可能感兴趣的:(redis)