Redis 发布订阅模式的深度解析与实现消息队列

news2025/1/12 18:43:31

1 发布订阅模式(Pub/Sub)的概述

我们可以利用Redis的List数据结构实现一

个简单的消息队列,通过lpush命令写入消息,通过rpop 命令拉取消息,也可以使用BRPOP实现阻塞式的拉取消息。

上面的消息队列有一个缺点,那就是不支持消息多播机制,消息多播机制就是生产者生产的一个消息可以被多个消费者消费到,这个功能在分布式系统中非常重要。

Redis单独使用Pub/Sub模块来支持消息多播,即发布/订阅模式(publish/subscribe),它是一种消息通信模式:发布者(pub)发送消息,订阅者(sub)接收消息。

发布者会将的消息发布到一个chanel(通道)中而不是发送给指定的订阅者,发布者也不知道可能有哪些订阅者。

订阅者可以订阅一个或多个channel,只接收来自订阅的channel的消息,并且不知道有哪些(如果有)发布者,这种模式实现了消息发布者和订阅者的解耦。

Pub/Sub 与键空间无关,消息不会被持久化,与数据库也无关,在db10上发布,将可以被 db1 上的订阅者听到。如果我们需要某种范围的范围,那么只能在设置的channel名字上做区分。

2 订阅

客户端使用SUBSCRIBE channel [channel ...]命令订阅通道,可以多次执行该命令,也可以一次订阅多个通道,多个客户端可以订阅相同的通道。

该命令返回一个数组,包括三部分,依次是:命令名称(字符串“subscribe”),订阅的通道名称,目前总共订阅的通道数(包含glob通道)。这三个部分对每一个订阅的通道是连续的。

客户端执行订阅以后,除了可以继续订阅(SUBSCRIBE或者PSUBSCRIBE),取消订阅(UNSUBSCRIBE或者PUNSUBSCRIBE), PING命令和结束连接(QUIT)外, 不能执行其他操作,客户端将阻塞直到订阅通道上发布消息的到来。

如下,表示客户端一次性订阅四个通道:aaa、bba、ccc、ddd:

127.0.0.1:6379> SUBSCRIBE aaa bba ccc ddd
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "aaa"
3) (integer) 1
1) "subscribe"
2) "bba"
3) (integer) 2
1) "subscribe"
2) "ccc"
3) (integer) 3
1) "subscribe"
2) "ddd"
3) (integer) 4

请注意,如果使用redis-cli 一旦进入订阅模式就不会接受任何命令,只能使用 Ctrl-C 退出该模式。

3 取消订阅

客户端使用UNSUBSCRIBE [channel [channel ...]]命令取消订阅指定的通道,可以指定一个或者多个取消的订阅通道名称,也可以不带任何参数,此时将取消所有的订阅的通道(不包括glob通道)。

该命令返回一个数组,包括三部分,依次是:命令名称(字符串“unsubscribe”),订阅的通道名称,目前总共订阅的通道数(包含glob通道)。这三个部分对每一个取消订阅的通道是连续的。当最后一个参数为零时,我们不再订阅任何频道,客户端可以发出任何类型的 Redis 命令,因为我们处于 Pub/Sub 状态之外。

如下,表示客户端退出ccccc通道的订阅:

127.0.0.1:6379> UNSUBSCRIBE ccccc
1) "unsubscribe"
2) "cccc"
3) (integer) 0

4 模式匹配

Redis Pub/Sub 实现支持模式匹配。客户端可以订阅 glob 通道,这样就能接收发送到通道名称与给定模式匹配的通道的所有消息。

客户端使用PSUBSCRIBE pattern [pattern ...] 订阅一个或多个glob 通道。

该命令返回一个数组,包括三部分,依次是:命令名称(字符串“psubscribe”),订阅的glob通道名称,目前总共订阅的通道数(包含非glob通道)。这三个部分对每一个订阅的通道是连续的。

例如,订阅a*和*c模式:

127.0.0.1:6379> PSUBSCRIBE a* *c
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "a*"
3) (integer) 1
1) "psubscribe"
2) "*c"
3) (integer) 2

客户端使用 PUNSUBSCRIBE [pattern [pattern ...]]退订一个或多个glob 通道。也可以不带任何参数,此时将取消所有的订阅的通道(不包括非glob通道)。

该命令返回一个数组,包括三部分,依次是:命令名称(字符串“punsubscribe”),取消订阅的glob通道名称,目前总共订阅的通道数(包含非glob通道)。这三个部分对每一个取消订阅的通道是连续的。当最后一个参数为零时,我们不再订阅任何频道,客户端可以发出任何类型的 Redis 命令,因为我们处于 Pub/Sub 状态之外。

如下,取消对a*的glob通道的订阅:

127.0.0.1:6379> PUNSUBSCRIBE a*
1) "punsubscribe"
2) "a*"
3) (integer) 0

subscribe, unsubscribe, psubscribe 和punsubscribe命令的最后都返回当前客户端订阅的glob通道和通道的总数,如果为0,则客户端自动退出Pub/Sub模式。

5 发布

PUBLISH channel message命令在指定的通道上发布消息。只能在一个通道上发布消息,不能在多个通道上同时发布消息。

将返回通知的接收者数量。这里的接收者数目大于等于订阅该通道的客户端数目,因为一个客户端的glob通道和非glob通道同时匹配发布通道的话,则视为两个接收者。换句话说,如果客户端订阅了多个与已发布消息匹配的模式,或者订阅了与该消息匹配的模式和通道,则该客户端可能会多次收到同一条消息。

在接收端,收到的响应包括三部分,依次是:“message”字符串,匹配的通道名称,发布的消息内容。如果是因为glob模式匹配而接收,那么返回四部分:“pmessage”字符串,匹配的glob通道名称,发送的原始通道名称,发布的消息内容。

如果某个客户端的订阅a*和*c两个模式通道:

127.0.0.1:6379> PSUBSCRIBE a* *c
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "a*"
3) (integer) 1
1) "psubscribe"
2) "*c"
3) (integer) 2

如果发送消息的通道为ac,那么将会返回2:

127.0.0.1:6379> PUBLISH ac xxxxx
(integer) 2

在客户端,将收到两次消息:

1) "pmessage"
2) "a*"
3) "ac"
4) "xxxxx"
1) "pmessage"
2) "*c"
3) "ac"
4) "xxxxx"

6 Pub/Sub原理

每个Redis服务器进程维持着一个标识服务器状态的redis.h/redisServer结构,其中就保存着有订阅的频道 以及 订阅模式 的信息:

struct redisServer {
    // ...
    dict *pubsub_channels;  // 订阅频道
    list *pubsub_patterns;  // 订阅模式
    // ...
};

6.1 pubsub_channels

pubsub_channels是一个dict字典结构,key(数组元素)为channel,value就是某个client。当客户端订阅某一个频道之后,Redis 就会往 pubsub_channels 这个字典中新添加一条channel和client数据,不同的client可以订阅相同的channel,client以链表的方式串联起来,这样就能保存多个client对同一个channel的关系,非常的巧妙。

了解了这个结构,SUBSCRIBE 、PUBLISH 、UNSUBSCRIBE命令的实现也变得十分简单了。

SUBSCRIBE就是将channel和client加入到dict中,如果此前没有该channel,那就新增一个channel元素,然后在再增一个client链表节点,如果此前存在,则直接在链表末尾添加一个client节点。

PUBLISH只需要通过上述字典定位到具体的channel,就能找到所有订阅该channel的客户端,再把消息发送给它们就好了。

UNSUBSCRIBE也很简单,将对应channel下面的链表中的client删除即可。

6.2 pubsub_patterns

pubsub_patterns用于存储所有的glob channel,它是一个list结构,节点类型为redis.h/pubsubPattern:

typedef struct pubsubPattern {
    redisClient *client;  // 订阅模式的客户端
    robj *pattern;        // 订阅的模式
} pubsubPattern;

当使用PSUBSCRIBE命令订阅一个模式时,程序就创建一个pubsubPattern添加到 pubsub_patterns 链表中。如果另一个客户端也订阅一个模式,则向链表的后面新增一个pubsubPattern节点即可。

因此,实际上PUBLISH除了会在pubsub_channels中定位具体的channel之外,还会将指定的channel与pubsub_patterns 中的模式进行对比,如果 指定的channel 和某个模式匹配的话,那么也将 message 发送到订阅那个模式的全部客户端。

PUNSUBSCRIBE的实现也很简单,就是删除pubsub_patterns中,client和pattern信息对比一致的节点。

7 Pub/Sub缺点

发布的消息在Redis系统中不能持久化,因此,必须先执行订阅,再等待消息发布。如果先发布了消息,那么该消息由于没有订阅者,消息将被直接丢弃。

消息只管发送,不管接收,也没有ACK机制,无法保证消息的消费成功。如果某个消费者中途加入进来,或者挂掉重启,那么这之前丢失的消息也不能再次消费。

以上的缺点导致Redis的Pub/Sub模式就像个小玩具,在生产环境中几乎无用武之地,非常的尴尬!为此,Redis5.0版本新增了Stream数据结构,不但支持多播,还支持数据持久化,相比Pub/Sub更加的强大!

8 相关文章

一、Redis简介、数据类型和命令

二、Redis数据类型介绍、使用场景及其操作命令

三、Redis事务的概述、设计与实现

四、Redis 发布订阅模式的深度解析与实现消息队列

五、Redis持久化RDB的三种触发机制及其优缺点

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/181996.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

百趣代谢组学文献分享:以猪为模型检测哺乳动物之间的代谢物交换

百趣代谢组学文献分享,您对哺乳动物不同器官之间的代谢物交换情况了解吗? 本期百趣代谢组学小趣给大家分享的是美国普林斯顿大学Joshua D. Rabinowitz团队发表在Cell Metabolism上的研究成果。该团队以猪为模型,利用高通量靶标技术定量测定了…

QEMU/KVM带与不带音频驱动参数的实际区别

实际工作中用到QEMU/KVM,按照书中的命令启动虚拟机后,发现Ubuntu镜像启动后找不到声卡设备,经过一番搜索和尝试,最终发现是“-device ac97”这一关键选项所导致的。现将具体的对比结果记录如下: 不带“-device ac97”…

2023编程语言趋势

2023编程语言趋势 作为CTO,我需要持续关注编程语言的发展。按照惯例,每年年初我都会对未来一年关键编程语言的趋势做一定的预判。今年由于众所周知的原因,预测地有些晚,我选择在开年的第一天给出我的预测,也算是祝大家…

(Java高级教程)第四章必备前端基础知识-第三节2:JavaScript数组、函数和对象

文章目录一:数组二:函数三:对象一:数组 数组:JavaScript中的数组和Java中的ArrayList有点相似,可以动态扩容,并且由于它是动态类型的语言,所以数组内的元素类型不要求一定是相同的 …

【Git】安装搭建与相关概念

目录 1. 安装 1.1出现安全警告,点击运行 1.2浏览协议,下一步 1.3安装目录,所需要磁盘空间大小,下一步 1.4Git Bash需要安装的,其他默认即可,下一步 1.5开始菜单,下一步 1.6默认编辑器&…

《MySQL高级篇》十三、锁

文章目录1. 概述2. MySQL并发事务访问相同记录2.1 读-读情况2.2 写-写情况2.3 读-写或写-读情况2.4 并发问题的解决方案3. 锁的不同角度分类3.1从数据操作的类型划分:读锁、写锁1. 锁定读2. 写操作3.2 从数据操作的粒度划分:表级锁、页级锁、行锁1. 表锁(Table Lock)① 表级别的…

JavaScript 练手小技巧:打字小游戏

放假闲来无事&#xff0c;一群小屁孩想玩我的电脑。 字都不会打&#xff0c;还玩电脑。 用 js 写一个打字游戏&#xff0c;打不到 100 分&#xff0c;就不要玩我的电脑~~~&#xff01;&#xff01;&#xff01; 整体界面如下所示&#xff0c;一切从简~ HTML 结构 <div i…

正则表达式-学习笔记

正则表达式&#xff08;Regular Expression&#xff09;是一种文本模式&#xff0c;包括普通字符&#xff08;例如&#xff1a;a到z之间的字母&#xff09;和特殊字符&#xff08;称为“元字符”&#xff09;。正则表达式使用单个字符串来描述、匹配一系列匹配某个句法规则的字…

企业如何利用生产制造业ERP管理系统做好采购管理?

采购对生产制造业企业而言&#xff0c;至关重要&#xff01;采购成本能够占到很多企业经营成本的60%左右&#xff0c;而所采购物料的质量直接决定了产品的质量。而在生产制造企业的采购工作中&#xff0c;经常会出现一些问题&#xff0c;比如&#xff1a;采购成本难控、采购流程…

Elasticsearch(五)--ES文档的操作(上)---写入文档

一、前言 使用ES构建搜索引擎时需要经常对文档进行操作&#xff0c;除了简单的单条文档操作&#xff0c;有时还需要进行批量操作。我们这章主要学习ES文档的增删改的操作&#xff0c;由于涉及到的代码量会比较多&#xff0c;所以分为3篇文章分别说明文档的这个三个操作。那么我…

星环科技TDH多模型统一架构VS CDH架构

CDH是Cloudera的开源平台发行版&#xff0c;通过将Hadoop与其他十几个开源项目集成&#xff0c;为企业大数据业务提供服务。 在CDH开源大数据方案中&#xff0c;是通过多个互相独立的组件提供相应的能力&#xff0c;每个场景需要一个组件独立交付&#xff0c;为了实现不同业务…

【SpringCloud】OpenFeign远程调用的基本使用

一、OpenFeign替代RestTemplateRestTemplate 存在的问题我们以前利用RestTemplate发起远程调用的代码public Order queryOrderById(Long orderId) {// 1.查询订单Order order orderMapper.findById(orderId);// 2.利用restTemplate发起http请求// 为了负载均衡使用服务名称Str…

跨境电商行业如何做好社交媒体营销?

随着互联网的快速发展,跨境电商行业也得到了快速的发展,跨境电商更是成为了当下最热门的话题之一,很多商家都想通过跨境电商平台来销售产品,但随着竞争越来越激烈,想要在众多卖家中脱颖而出,就需要从营销方面入手了&#xff0c;这就意味着卖家们需要掌握一定的营销技巧。而在现…

jsp+SSM368的药品销售配送网站系统maven

管理员登录&#xff0c;管理员通过输入用户名、密码、角色等信息进行系统登录 管理员登录进入药品销售系统可以查看&#xff1b;个人中心、用户管理、医生管理、药品信息管理、药品分类管理、订单配送管理、系统管理、订单管理等内容 个人信息&#xff0c;个人信息页面可以填写…

Swig/CPP2Java

简介 实际工程可能存在如下部分&#xff1a;业务接口需要编程高效的语言&#xff08;如Python、Java等&#xff09;&#xff0c;易于部署维护&#xff1b;而核心算法部分&#xff0c;某些场景需要高效计算&#xff0c;会使用性能高效的语言&#xff08;如C/C等&#xff09;。 …

超详细Netty入门,看这篇就够了!

简介&#xff1a; 本文主要讲述Netty框架的一些特性以及重要组件&#xff0c;希望看完之后能对Netty框架有一个比较直观的感受&#xff0c;希望能帮助读者快速入门Netty&#xff0c;减少一些弯路。 前言 本文主要讲述Netty框架的一些特性以及重要组件&#xff0c;希望看完之后…

一起自学SLAM算法:10.2 VINS算法

连载文章&#xff0c;长期更新&#xff0c;欢迎关注&#xff1a; 不管是激光SLAM还是视觉SLAM&#xff0c;由于传感器采样率、传感器测量精度、主机计算力等因素的限制&#xff0c;在高速运动状态下定位追踪极易丢失。虽然轮式里程计能为激光SLAM系统提供短期运动预测以避免高速…

记录一次ubuntu进入不了界面的恢复记录

能说服一个人的从来不是道理&#xff0c;而是南墙&#xff1b;能点醒一个人的从来不是说教&#xff0c;而是磨难 一、问题描述 1、 卸载Python之后&#xff0c;ubuntu启动进入黑屏tty界面无法联网&#xff0c;无法进入桌面 2、 进入到界面之后没有网络&#xff0c;网络中或者右…

【分析向】没有三级缓存会导致什么?

通过上篇&#xff08;【实践向】当移除了三级缓存…… &#xff09;的实践&#xff0c;我们得出的结论是&#xff1a;如果不存在代理对象&#xff0c;二级缓存就可以解决循环依赖性的问题&#xff0c;但是当存在代理对象的时候&#xff0c;二级缓存则无法完全解决循环依赖&…

机器自动翻译古文拼音 - 十大宋词 - ALL

机器自动翻译古文拼音 - 十大宋词 - 雨霖铃寒蝉凄切 柳永https://mp.csdn.net/mp_blog/creation/editor/128779245机器自动翻译古文拼音 - 十大宋词 - 江城子乙卯正月二十日夜记梦 苏轼https://mp.csdn.net/mp_blog/creation/editor/128779156机器自动翻译古文拼音 - 十大宋词 …