概要
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的发布订阅模式pubsub
前言 redis支持发布订阅模式,在这个实现中,发送者(发送信息的客户端)不是将信息直接发送给特定的接收者(接收信息的客户端),而是将信息发送给频道(channel),然后由频道将信息转发给所有对这个 ...
- 13、Redis的发布订阅模式
写在前面的话:读书破万卷,编码如有神 -------------------------------------------------------------------------------- ...
- Springboot+Redis(发布订阅模式)跨多服务器实战
一:redis中发布订阅功能(http://www.redis.cn/commands.html#pubsub) PSUBSCRIBE pattern [pattern -]:订阅一个或者多个符合pa ...
- 【转】Redis之发布 订阅模式
本例包括 jedis_demo:入口类 jedis_control:jedis控制器(jedis的连接池) jedis_pub_sub_listener:订阅的监听器 singleton_agent: ...
- 使用redis的发布订阅模式实现消息队列
配置文件 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://w ...
- Spring Boot中使用redis的发布/订阅模式
原文:https://www.cnblogs.com/meetzy/p/7986956.html redis不仅是一个非常强大的非关系型数据库,它同时还拥有消息中间件的pub/sub功能,在sprin ...
- 15天玩转redis —— 第九篇 发布/订阅模式
本系列已经过半了,这一篇我们来看看redis好玩的发布订阅模式,其实在很多的MQ产品中都存在这样的一个模式,我们常听到的一个例子 就是邮件订阅的场景,什么意思呢,也就是说100个人订阅了你的博客,如果 ...
- redis发布/订阅模式
其实在很多的MQ产品中都存在这样的一个模式,我们常听到的一个例子 就是邮件订阅的场景,什么意思呢,也就是说100个人订阅了你的博客,如果博主发表了文章,那么100个人就会同时收到通知邮件,除了这个 场 ...
- 使用EventBus + Redis发布订阅模式提升业务执行性能
前言 最近一直奔波于面试,面了几家公司的研发.有让我受益颇多的面试经验,也有让我感觉浪费时间的面试经历~因为疫情原因,最近宅在家里也没事,就想着使用Redis配合事件总线去实现下具体的业务. 需求 一 ...
随机推荐
- MySQL基础之第17章 MySQL日志
17.1.日志简介 二进制日志错误日志通用查询日志慢查询日志 17.2.二进制日志 二进制日志也叫作变更日志(update log),主要用于记录数据库的变化情况.通过二进制日志可以查询MySQL数据 ...
- arcgis for android 无法加载本地jpg影像解决办法
因为jpg影像没有生成金子塔文件*.rrd 一个完整的JPG影像必须包括如下文件: K-50-96-(16).aux 辅助文件K-50-96-(16).jgw 坐标信息K-50-96-(16).j ...
- ASP.NET Web API 帮助(help)页面上没有 Test API按钮的解决方法
参与一个web API项目时发现它的help页面特别好用,不仅可以根据webapi的方法和注释自动生成帮助文档以方便查阅,还可以在这个页面上测试webapi方法.于是在自己新建项目时也打算将这个hel ...
- IntelliJ IDEA svn 提交错误
环境说明: 系统:Mac OS X 10.9 以及 10.10 系统设置:LANG=zh_CN.UTF-8 svn 客户端:1.8.10 IntelliJ IDEA 13 毫无疑问,IntelliJ ...
- FontMetrics ----- 绘制文本,获取文本高度
Canvas 绘制文本时,使用FontMetrics对象,计算位置的坐标. public static class FontMetrics { /** * The maximum distance a ...
- JavaScript专业规则12条
学习JavaScript是困难的.它发展的如此之快,以至于在任何一个特定的时刻,你都不清楚自己是否“做错了”.有些时候,感觉像是坏的部分超过了好的部分.然而,讨论这些并没有意义,JavaScript正 ...
- ajax给全局变量赋值问题
ajax给全局变量赋值问题 今天在做项目时,遇到了一个问题.我用的是ajax,要在$.ajax({里面给一个全局变量赋值,结果死活赋值不上,纠结了好半天,后来上网查了查,才知道,ajax默认是异步请求 ...
- ansible控制windows的官方翻译
Ansible控制windows 1. Windows下如何工作 在ansible控制linux的时候,用的是ssh的方式,在windows中,使用的是power shell,在客户端机器上也是 ...
- nagios-解决监控页面上的乱码
1. 前景 在nagios的监控页面上发现返回来的信息为乱码,如下图所示: 查看相关日志,发现正常显示汉字,如下: 2. 解决方法(以下两个步骤缺一不可) 主要原因分析如下: 在nagios服务器上发 ...
- Name-based virtual servers 给予名称的虚拟服务
nginx first decides which server should process the request. Let’s start with a simple configuration ...