概要
redis的每个server实例都维护着一个保存服务器状态的redisServer结构
struct redisServer
{
    /* Pubsub */
    // 字典,键为频道,值为链表
    // 链表中保存了所有订阅某个频道的客户端
    // 新客户端总是被添加到链表的表尾
    dict *pubsub_channels;  /* Map channels to list of subscribed clients */
    // 这个链表记录了客户端订阅的所有模式的名字
    list *pubsub_patterns;  /* A list of pubsub_patterns */
};
pubsub_channels记录了所有客户端订阅的频道的信息。数据类型是dict,dict中的key代表了channel,而val存储了关注频道的所有clients。
redis的发布订阅模式

订阅者通过sub命令订阅频道,server使用pub命令把消息推送到符合条件的pubsub_channels中。
内部数据结构
pubsub_channels是一个字典结构,字典内部使用hash表存储和索引数据。
实现字典的可选数据结构
    hash表:简单但是不稳定的基于数组的冲突解决法;简单且平均效率稳定的链式地址的冲突解决法;
    字典树:使用树结构。
redis采用hash的链式地址法作为实现,好处是直观、简单、可期待平均时间复杂度较小且平稳。
typedef struct dict {
    // 类型特定函数
    dictType *type;
    // 私有数据
    void *privdata;
    // 哈希表
    dictht ht[2];
    // rehash 索引
    // 当 rehash 不在进行时,值为 -1
    int rehashidx; /* rehashing not in progress if rehashidx == -1 */
    // 目前正在运行的安全迭代器的数量
    int iterators; /* number of iterators currently running */
} dict;
dict使用两个hash表进行索引,当进行rehash的时候使用ht[1]的hash表,其他时候都使用hd[0]的hash表。
使用的hash表的定义如下
typedef struct dictht {
    // 哈希表数组
    dictEntry **table;
    // 哈希表大小
    unsigned long size;
    // 哈希表大小掩码,用于计算索引值
    // 总是等于 size - 1
    unsigned long sizemask;
    // 该哈希表已有节点的数量
    unsigned long used;
} dictht;
table是一个存储dictEntry*指针的数组,table中的每一项都是一个指向DictEntry结构的指针,同时每一项也都带有一个指向下一项的指针。可以看出这是一个使用链地址法解决冲突的hash结构。
dictEntry的定义,包含key、value、next。
/*
 * 哈希表节点
 */
typedef struct dictEntry {
    // 键
    void *key;
    // 值
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
    } v;
    // 指向下个哈希表节点,形成链表
    struct dictEntry *next;
} dictEntry;
客户端订阅频道
就是字典中插入新元素的过程
        // 关联示意图
        // {
        //  频道名        订阅频道的客户端
        //  'channel-a' : [c1, c2, c3],
        //  'channel-b' : [c5, c2, c1],
        //  'channel-c' : [c10, c2, c1]
        // }
        /* Add the client to the channel -> list of clients hash table */
        // 从 pubsub_channels 字典中取出保存着所有订阅了 channel 的客户端的链表
        // 如果 channel 不存在于字典,那么添加进去
        de = dictFind(server.pubsub_channels,channel);
        if (de == NULL) {
            clients = listCreate();
            dictAdd(server.pubsub_channels,channel,clients);
            incrRefCount(channel);
        } else {
            clients = dictGetVal(de);
        }
        // before:
        // 'channel' : [c1, c2]
        // after:
        // 'channel' : [c1, c2, c3]
        // 将客户端添加到链表的末尾
        listAddNodeTail(clients,c);
1.查询server是否包含指定频道
2.如果频道存在就获取频道指向的list地址;如果频道不存在,就创建频道和list
3.使用步骤2的list,将客户端添加到list的末尾。
完成这三步以后,server维护的发布订阅频道就新增了一个频道和关注的客户端,server发布时检测发布订阅字典,获取订阅客户端并依次发送。
server发布消息到channel
/* Send to clients listening for that channel */
    // 取出包含所有订阅频道 channel 的客户端的链表
    // 并将消息发送给它们
    de = dictFind(server.pubsub_channels,channel);
    if (de) {
        list *list = dictGetVal(de);
        listNode *ln;
        listIter li;
        // 遍历客户端链表,将 message 发送给它们
        listRewind(list,&li);
        while ((ln = listNext(&li)) != NULL) {
            redisClient *c = ln->value;
            // 回复客户端。
            // 示例:
            // 1) "message"
            // 2) "xxx"
            // 3) "hello"
            addReply(c,shared.mbulkhdr[3]);
            // "message" 字符串
            addReply(c,shared.messagebulk);
            // 消息的来源频道
            addReplyBulk(c,channel);
            // 消息内容
            addReplyBulk(c,message);
            // 接收客户端计数
            receivers++;
        }
    }
结合订阅者订阅的过程,发布过程就是一个查找订阅者并轮询发送消息给订阅者的过程。

redis的发布订阅模式的更多相关文章

  1. redis的发布订阅模式pubsub

    前言 redis支持发布订阅模式,在这个实现中,发送者(发送信息的客户端)不是将信息直接发送给特定的接收者(接收信息的客户端),而是将信息发送给频道(channel),然后由频道将信息转发给所有对这个 ...

  2. 13、Redis的发布订阅模式

     写在前面的话:读书破万卷,编码如有神 -------------------------------------------------------------------------------- ...

  3. Springboot+Redis(发布订阅模式)跨多服务器实战

    一:redis中发布订阅功能(http://www.redis.cn/commands.html#pubsub) PSUBSCRIBE pattern [pattern -]:订阅一个或者多个符合pa ...

  4. 【转】Redis之发布 订阅模式

    本例包括 jedis_demo:入口类 jedis_control:jedis控制器(jedis的连接池) jedis_pub_sub_listener:订阅的监听器 singleton_agent: ...

  5. 使用redis的发布订阅模式实现消息队列

    配置文件 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://w ...

  6. Spring Boot中使用redis的发布/订阅模式

    原文:https://www.cnblogs.com/meetzy/p/7986956.html redis不仅是一个非常强大的非关系型数据库,它同时还拥有消息中间件的pub/sub功能,在sprin ...

  7. 15天玩转redis —— 第九篇 发布/订阅模式

    本系列已经过半了,这一篇我们来看看redis好玩的发布订阅模式,其实在很多的MQ产品中都存在这样的一个模式,我们常听到的一个例子 就是邮件订阅的场景,什么意思呢,也就是说100个人订阅了你的博客,如果 ...

  8. redis发布/订阅模式

    其实在很多的MQ产品中都存在这样的一个模式,我们常听到的一个例子 就是邮件订阅的场景,什么意思呢,也就是说100个人订阅了你的博客,如果博主发表了文章,那么100个人就会同时收到通知邮件,除了这个 场 ...

  9. 使用EventBus + Redis发布订阅模式提升业务执行性能

    前言 最近一直奔波于面试,面了几家公司的研发.有让我受益颇多的面试经验,也有让我感觉浪费时间的面试经历~因为疫情原因,最近宅在家里也没事,就想着使用Redis配合事件总线去实现下具体的业务. 需求 一 ...

随机推荐

  1. Github托管代码步骤

    总结一下Github上项目托管步骤: Step 1:注册Github帐号,并创建一个repository,假如仓库名为test: Step 2:安装本地Git: sudo apt-get instal ...

  2. Android 程序员必须掌握的三种自动化测试方法

    在日常的开发中,尤其是app开发,因为不像web端那样 出错以后可以热更新,所以app开发 一般对软件质量有更高的要求(你可以想一下 一个发出去的版本如果有重大缺陷 需要强制更新新客户端是多么蛋疼的事 ...

  3. JS面向对象组件(六) -- 拖拽功能以及组件的延展

    HTML部分 <div id="div1"></div> <div id="div2"></div> CSS部分 ...

  4. 通过反射执行get、set方法

    Class clazz = sourceObj.getClass(); 1.获取所有属性 BeanInfo beanInfo = Introspector.getBeanInfo(clazz); Pr ...

  5. 网络编辑基础:对HTTP协议的头信息详解

    HTTP(HyperTextTransferProtocol) 是超文本传输协议的缩写,它用于传送WWW方式的数据,关于HTTP 协议的详细内容请参 考RFC2616.HTTP协议采用了请求/响应模型 ...

  6. ios 内存管理 心得

    - alloc, copy, retain会把引用计数+1 - release会把引用计数-1 - 局部变量如果初始化时不是autorelease的,要及时调用release释放,并且赋值为nil否则 ...

  7. Android学习随笔--ListView的分页功能

    第一次写博客,可能格式,排版什么的会非常不美观,不过我主要是为了记录自己的Android学习之路,为了以后能有些东西回顾.既然是为了学习,那我肯定会吸收各位大大们的知道经验,有不足的地方请指出. 通过 ...

  8. [Everyday Mathematics]20150220

    试求 $$\bex \sum_{k=0}^\infty\frac{1}{(4k+1)(4k+2)(4k+3)(4k+4)}. \eex$$

  9. Android开发中这些小技巧

    http://blog.csdn.net/guxiao1201/article/details/40655661 http://blog.csdn.net/guxiao1201/article/det ...

  10. [转]Loadrunner11之VuGen运行时设置Run-Time Setting

    转自:http://www.51testing.com/html/92/450992-248065.html General 1.Run Logic运行逻辑 脚本如何运行的,每个action和acti ...