一文带你了解 Redis 的发布与订阅的底层原理
01、前言
发布订阅系统在我们日常的工作中经常会使用到,这种场景大部分情况我们都是使用消息队列的,常用的消息队列有 Kafka,RocketMQ,RabbitMQ,每一种消息队列都有其特性,关于 Kafka 的使用和源码分析,公号前面有相关的文章,大家可以前往回顾一下,另外两款消息队列大家有需要可以自行研究,后续我们会出相应的介绍文章。这篇文章主要是给大家介绍 Redis 的发布订阅系统,很多时候我们可能不需要独立部署相应的消息队列,只是简单的使用,而且数据量也不会太大,这种情况下,我们就可以使用 Redis 的 Pub/Sub 模型。
02、使用方式
2.1 发布与订阅
Redis 的发布订阅功能主要由 PUBLISH,SUBSCRIBE,PSUBSCRIBE 命令组成,一个或者多个客户端订阅某个或者多个频道,当其他客户端向该频道发送消息的时候,订阅了该频道的客户端都会收到对应的消息。
上图中有四个客户端,Client 02,Client 03,Client 04 订阅了同一个 Sport 频道(Channel),这时当 Client 01 向 Sport Channel 发送消息 “basketball” 的时候,02-04 这三个客户端都同时收到了这条消息。
整个过程的执行命令如下:
首先开四个 Redis 的客户端,然后在 Client 02,Client 03,Client 04 中输入subscribe sport 命令,表示订阅 sport 这个频道
然后在 Client 01 的客户端中输入publish sport basketball 表示向 sport 频道发送消息 "basketball"
这个时候我们在去看下 Client 02-04 的客户端,可以看到已经收到了消息了,每个订阅了这个频道的客户端都是一样的。
这里 Client 02-Client 04 三个客户端订阅了 Sport 频道,我们叫做订阅者(subscriber),Client 01 发布消息,我们叫做发布者(publisher),发送的消息就是 message。
2.2、模式订阅
前面我们看到的是一个客户端订阅了一个 Channel,事实上单个客户端也可以同时订阅多个 Channel,采用模式匹配的方式,一个客户端可以同时订阅多个 Channel。
如上图 Client 05 通过命令subscribe run 订阅了 run 频道,Client 06 通过命令psubscribe run* 订阅了 run* 匹配的频道。当 Client 07 向 run频道发送消息 666 的时候,05 和 06 两个客户端都收到消息了;接下来 Client 07 向 run1 和 run_sport 两个频道发送消息的时候,Client 06 依旧可以收到消息,而 Client 05 就收不到了消息了。
Client 05 订阅run 频道和接收到消息:
Client 06 订阅run* 频道和接收到消息:
Client 07 向多个频道发送消息:
通过上面的案例,我们学会了一个客户端可以订阅单个或者多个频道,分别通过subscribe,psubscribe 命令,客户端可以通过 publish 发送相应的消息。
在命令行中我们可以用 Ctrl + C 来取消相关订阅,对应的命令时 unsubscribe channelName。
03、Pub/Sub 底层存储结构
3.1、订阅 Channel
在 Redis 的底层结构中,客户端和频道的订阅关系是通过一个字典加链表的结构保存的,形式如下:
在 Redis 的底层结构中,Redis 服务器结构体中定义了一个pubsub_channels 字典
structredisServer{//用于保存所有频道的订阅关系dict *pubsub_channels;}
在这个字典中,key 代表的是频道名称,value 是一个链表,这个链表里面存放的是所有订阅这个频道的客户端。
所以当有客户端执行订阅频道的动作的时候,服务器就会将客户端与被订阅的频道在 pubsub_channels 字典中进行关联。
这个时候有两种情况:
- 该渠道是首次被订阅:首次被订阅说明在字典中并不存在该渠道的信息,那么程序首先要创建一个对应的 key,并且要赋值一个空链表,然后将对应的客户端加入到链表中。此时链表只有一个元素。
- 该渠道已经被其他客户端订阅过:这个时候就直接将对应的客户端信息添加到链表的末尾就好了。
比如,如果有一个新的客户端 Client 08 要订阅 run 渠道,那么上图就会变成
如果 Client 08 要订阅一个新的渠道 new_sport ,那么就会变成
整个订阅的过程可以采用下面伪代码来实现
Map<String, List<Object>> pubsub_channels = new HashMap<>();
publicvoidsubscribe(String[] subscribeList, Object client){
//遍历所有订阅的 channel,检查是否在 pubsub_channels 中,不在则创建新的 key 和空链表
for (int i = 0; i < subscribeList.length; i++) {
if (!pubsub_channels.containsKey(subscribeList[i])) {
pubsub_channels.put(subscribeList[i], new ArrayList<>());
}
pubsub_channels.get(subscribeList[i]).add(client);
}
}
3.2 取消订阅
上面介绍的是单个 Channel 的订阅,相反的如果一个客户端要取消订阅相关 Channel,则无非是找到对应的 Channel 的链表,从中删除对应的客户端,如果该客户端已经是最后一个了,则将对应 Channel 也删除。
04、模式订阅结构
模式渠道的订阅与单个渠道的订阅类似,不过服务器是将所有模式的订阅关系都保存在服务器状态的pubsub_patterns 属性里面。
与订阅单个 Channel 不同的是,pubsub_patterns 属性是一个链表,不是字典。节点的结构如下:
其实 client 属性是用来存放对应客户端信息,pattern 是用来存放客户端对应的匹配模式。
所以对应上面的 Client-06 模式匹配的结构存储如下
在pubsub_patterns链表中有一个节点,对应的客户端是 Client-06,对应的匹配模式是run*。
4.1、订阅模式
当某个客户端通过命令psubscribe 订阅对应模式的 Channel 时候,服务器会创建一个节点,并将 Client 属性设置为对应的客户端,pattern 属性设置成对应的模式规则,然后添加到链表尾部。
对应的伪代码如下:
- 创建新节点;
- 给节点的属性赋值;
- 将节点添加到链表的尾部;
4.2、退订模式
退订模式的命令是punsubscribe,客户端使用这个命令来退订一个或者多个模式 Channel。服务器接收到该命令后,会遍历pubsub_patterns链表,将匹配到的 client 和 pattern 属性的节点给删掉。这里需要判断 client 属性和 pattern 属性都合法的时候再进行删除。
伪代码如下:
遍历所有的节点,当匹配到相同 client 属性和 pattern 属性的时候就进行节点删除。
05、发布消息
发布消息比较好容易理解,当一个客户端执行了publish channelName message 命令的时候,服务器会从pubsub_channels和pubsub_patterns 两个结构中找到符合channelName 的所有 Channel,进行消息的发送。在 pubsub_channels 中只要找到对应的 Channel 的 key 然后向对应的 value 链表中的客户端发送消息就好。
06、总结
这篇文章主要给大家介绍了一下 Redis 的发布/订阅的使用方式和底层的存储结构以及部分伪代码的实现,希望对大家有帮助。
一文带你了解 Redis 的发布与订阅的底层原理的更多相关文章
- redis实现发布(订阅)消息
redis实现发布(订阅)消息 什么是redis的发布订阅(pub/sub)? Pub/Sub功能(means Publish, Subscribe)即发布及订阅功能.基于事件的系统中,Pub/S ...
- Redis的发布和订阅
Redis的发布和订阅 Redis发布订阅(pub/sub)是一种消息通信模式,pub发布消息,sub接收消息.(pub/sub)是一种生产者消费者模式,是实现消息队列的一种方式 redis的订阅和发 ...
- Redis的发布与订阅
业务: 运用数据与信息指导小药工的采购生产与销售行为 需求背景: (1)药工汇小程序用户(即小型中药初加工用户)需要知道自己加工的品种的价格涨跌信息和品种相关资讯) 需求分析拆解: (1)使用爬虫程序 ...
- redis的发布与订阅机制
Redis 发布/订阅机制原理分析 Redis 通过 PUBLISH. SUBSCRIBE 和 PSUBSCRIBE 等命令实现发布和订阅功能. 这些命令被广泛用于构建即时通信应用,比如网络聊天室(c ...
- redis:消息发布与订阅频道
1. 发布与订阅频道 消息发布与订阅像收音机与广播台的关系 1.1. publish channel message 发布频道 语法:publish channel message 作用:发布频道消息 ...
- 从Redis分布式缓存实战入手到底层原理分析、面面俱到覆盖大厂面试考点
概述 官方说明 Redis官网 https://redis.io/ 最新版本6.2.6 Redis中文官网 http://www.redis.cn/ 不过中文官网的同步更新维护相对要滞后不少时间,但对 ...
- 一文带你了解Redis持久化完整版本
本文讲解知识点 持久化的简介 RDB AOF RDB与AOF的区别 持久化应用场景 对于持久化这个功能点,其实很简单没有那么复杂 演示环境 centos7.0 redis4.0 redis存放目录:/ ...
- 一文带你认知定时消息发布RocketMQ
摘要:DMS任意时间定时消息能力发布. DMS是华为云的分布式消息中间件服务.适用于解决分布式架构中的系统解耦.跨系统跨地域数据流通.分布式事务协调等难题,协助构建优雅的现代化应用架构,提供可兼容 K ...
- springboot使用redis实现发布与订阅
配置redis连接地址 # Redis服务器地址 spring.redis.host=youxiu326.xin # Redis服务器连接端口 spring.redis.port=6379 # Red ...
随机推荐
- 截取字符串长度,超出部分用省略号代替 PHP
function subText($text, $length){ if (mb_strlen($text, 'utf8') > $length) { return mb_substr($tex ...
- Spring Security 入门篇
本文是一个笔记系列,目标是完成一个基于角色的权限访问控制系统(RBAC),有基本的用户.角色.权限管理,重点在Spring Security的各种配置.万丈高楼平地起,接下来,一步一步,由浅入深,希望 ...
- Redis数据结构—跳跃表
目录 Redis数据结构-跳跃表 跳跃表产生的背景 跳跃表的结构 利用跳跃表查询有序链表 Redis跳跃表图示 Redis跳跃表数据结构 小结 Redis数据结构-跳跃表 大家好,我是白泽,最近学校有 ...
- BD-rate的计算
相信不少接触视频编码的朋友在看相关的文献的时候,总会看到论文中测试时给出一个重要的参数BD-rate,可能一直心存疑问,这个BD-rate到底是个什么东西呢?可以参考这一份提案http://downl ...
- 【BUAA软工】提问回顾与个人总结
链接到以前提问题的博客 在之前的博客我曾经提问过以下几个问题 为什么单元测试必须由写程序的人完成? 过早优化,过早泛华:何时为过早? 为何使用goto语句? 用户需求分析:分而治之,如何分? 兼容性测 ...
- 不同规模的企业对CRM的需求是否相同?
CRM客户管理系统在我们的认知中往往是中大型企业的选择.如今,越来越多中小规模企业开始使用CRM系统.CRM的功能随着发展变得越来越实用,可以满足不同行业不同业务规模的企业的需求.同时,CRM功能类型 ...
- MySQL中MyISAM为什么比InnoDB查询快
大家都知道在MySQL中,MyISAM比InnoDB查询快,但很多人都不知道其中的原理. 今天我们就来聊聊其中的原理,另外也验证下是否MyISAM比InnoDB真的查询快. 在探索其中原理之前,我们先 ...
- 微服务·API文档
阅文时长 | 3.92分钟 字数统计 | 2754.05字符 主要内容 | 1.什么是API文档 2.API文档的使用 3.声明与参考资料 『微服务·API文档』 编写人 | SCscHero 编写时 ...
- Shell 脚本重启项目
每次发打包好项目后都需要手动重启项目,写个Shell脚本一键重启项目 Shell 脚本 #!/bin/bash while getopts "n:p:" arg do case $ ...
- 059.Python前端Django组件cooki和session
一 会话跟踪技术 1.1 什么是会话 会话是指一个终端用户(服务器)与交互系统(客户端)进行通讯的过程. 1.2 什么是会话跟踪 对同一个用户对服务器的连续的请求和接受响应的监视.(将用户与同一用户发 ...