zoukankan      html  css  js  c++  java
  • Redis发布订阅模式-2

    实现原理

    有兴趣看Redis pub/sub源码的可以移步这里,有中文注释的哦,感谢作者的奉献。

    订阅(Subscribe)

    先直接上源码

    /*
     * 为客户端订阅指定的频道
     * 
     * 订阅成功返回 1 ,如果频道已经订阅,返回 0 。
     *
     * T = O(1)
     */
    int pubsubSubscribeChannel(redisClient *c, robj *channel) {
        struct dictEntry *de;
        list *clients = NULL;
        int retval = 0;
    
        /* Add the channel to the client -> channels hash table */
        // 将 channel 添加到客户端的 pubsub_channels 字典中
        // 键为 channel ,值为 NULL
        // O(1)
        if (dictAdd(c->pubsub_channels,channel,NULL) == DICT_OK) {
            retval = 1;
            incrRefCount(channel);
            /* Add the client to the channel -> list of clients hash table */
            // 将客户端添加到频道的订阅链表里
            // 其中 server.pubsub_channels 为字典
            // 字典的键为 channel ,值为链表,链表里保存了订阅该频道的所有客户端
            // O(1)
            de = dictFind(server.pubsub_channels,channel);
            if (de == NULL) {
                clients = listCreate();
                // O(1)
                dictAdd(server.pubsub_channels,channel,clients);
                incrRefCount(channel);
            } else {
                clients = dictGetVal(de);
            }
            // O(1)
            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;
    }
    

    主要做了两件事情

    1. 判断新的channel是否存在于客户端的pubsub_channels中,如果不存在执行下面步骤

    2. 将channel添加到客户端的pubsub_channels的哈希表中。

    3. 将客户端添加到server.pubsub_channels字典中channel的链表中。

      1588233682929

    模式订阅

    /* Subscribe a client to a pattern. Returns 1 if the operation succeeded, or 0 if the clinet was already subscribed to that pattern. */
    /*
     * 订阅一个模式
     *
     * 订阅成功返回 1 ,如果已经订阅返回 0 。
     *
     * T = O(N)
     */
    int pubsubSubscribePattern(redisClient *c, robj *pattern) {
        int retval = 0;
    
        // 如果 pattern 未保存在 c->pubsub_patterns 链表,那么。。。
        // O(N)
        if (listSearchKey(c->pubsub_patterns,pattern) == NULL) {
            retval = 1;
            pubsubPattern *pat;
    
            // 将模式加入客户端链表, O(1)
            listAddNodeTail(c->pubsub_patterns,pattern);
    
            incrRefCount(pattern);
    
            // 将模式和客户端信息记录到服务器
            // O(1)
            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;
    }
    

    也是做了两件事情

    1. 判断新的pattern是否存在于客户端的pubsub_patterns链表中,如果存在继续下面步骤

    2. 将新的pattern加入客户端的pubsub_patterns链表中,可以看到这里使用的是链表而不是哈希表,主要是因为发送消息的时候,pattern模式每次都需要遍历所有的项来匹配适合,所以不需要哈希表这种通过键值查找的数据结构

    3. 新建一个新的pattern,指向client,并添加到server.pubsub_patterns链表尾部,这里需要注意的是不管新的pattern是否在链表中存在,都是会新建一个对象插入到链表尾部。

      1588236058868

    退订/退订模式

    退订的话就是相当于做了个逆向操作,将订阅的时候添加的东西进行删除。

    发布

    /* 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 */
        // 取出所有订阅给定频道的客户端, O(1)
        de = dictFind(server.pubsub_channels,channel);
        if (de) {
            list *list = dictGetVal(de);
            listNode *ln;
            listIter li;
    
            // 将信息发送至至所有订阅者, O(N)
            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 */
        // 匹配的数量不为 0 
        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;
    }
    
    1. 取出所有订阅给定频道的客户端,将信息发送至至所有订阅者.

    2. 判断服务端模式的链表不为空,则遍历所有模式,并将信息发送给匹配的模式。

      从这里也可以看出上篇文章所说的,订阅一个channel再订阅一个匹配这个channel的pattern,会多次发送消息。

  • 相关阅读:
    使用Angular2建立项目
    你不知道的JavaScript-0
    项目从0到1
    登录页跳转时保存用户信息-遇坑记
    robotframework-ride快捷方式打不开
    win10 python3.8 wxpython.whl 安装步骤
    QPS
    xmind 文件 打开后会在当前目录生成 configuration,p2和workspace目录,artifacts.xml文件 解决
    JMeter进行压力测试时查看服务器的资源使用
    JMeter和JMeterPlugin的下载安装
  • 原文地址:https://www.cnblogs.com/rstar/p/12844167.html
Copyright © 2011-2022 走看看