• 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;
    }


  • 相关阅读:
    host 文件位置
    Django 前后端分离开发配置 跨域
    pycharm 关闭单词拼写检查(Typo: In word 'cacheable' )
    Python : argument name should be lowercase 警告处理解决方法
    pycharm 变量名 (Shadows built-in name 'id' )问题
    三体
    12.URL下载网络资源
    11.UDP多线程在线咨询
    10.UDP实现聊天
    9.UDP
  • 原文地址:https://www.cnblogs.com/suncoolcat/p/3299590.html
Copyright © 2020-2023  润新知