《消息队列高手课》主题和队列有什么区别?

2023-10-30

如果你研究过超过一种消息队列产品,你可能已经发现,每种消息队列都有自己的一套消息模型,像队列(Queue)、主题(Topic)或是分区(Partition)这些名词概念,在每个消息队列模型中都会涉及一些,含义还不太一样。

为什么出现这种情况呢?因为没有标准。曾经,也是有一些国际组织尝试制定过消息相关的标准,比如早期的 JMS 和 AMQP。但让人无奈的是,标准的进化跟不上消息队列的演进速度,这些标准实际上已经被废弃了。

那么,到底什么是队列?什么是主题?主题和队列又有什么区别呢?想要彻底理解这些,我们需要从消息队列的演进说起。

主题和队列有什么区别?

在互联网的架构师圈儿中间,流传着这样一句不知道出处的名言,我非常认同和喜欢:好的架构不是设计出来的,而是演进出来的。 现代的消息队列呈现出的模式,一样是经过之前的十几年逐步演进而来的。

最初的消息队列,就是一个严格意义上的队列。在计算机领域,“队列(Queue)”是一种数据结构,有完整而严格的定义。在维基百科中,队列的定义是这样的:

队列是先进先出(FIFO, First-In-First-Out)的线性表(Linear List)。在具体应用中通常用链表或者数组来实现。队列只允许在后端(称为 rear)进行插入操作,在前端(称为 front)进行删除操作。

这个定义里面包含几个关键点,第一个是先进先出,这里面隐含着的一个要求是,在消息入队出队过程中,需要保证这些消息严格有序,按照什么顺序写进队列,必须按照同样的顺序从队列中读出来。不过,队列是没有“读”这个操作的,“读”就是出队,也就是从队列中“删除”这条消息。

**早期的消息队列,就是按照“队列”的数据结构来设计的。**我们一起看下这个图,生产者(Producer)发消息就是入队操作,消费者(Consumer)收消息就是出队也就是删除操作,服务端存放消息的容器自然就称为“队列”。

这就是最初的一种消息模型:队列模型。

 

如果有多个生产者往同一个队列里面发送消息,这个队列中可以消费到的消息,就是这些生产者生产的所有消息的合集。消息的顺序就是这些生产者发送消息的自然顺序。如果有多个消费者接收同一个队列的消息,这些消费者之间实际上是竞争的关系,每个消费者只能收到队列中的一部分消息,也就是说任何一条消息只能被其中的一个消费者收到。

如果需要将一份消息数据分发给多个消费者,要求每个消费者都能收到全量的消息,例如,对于一份订单数据,风控系统、分析系统、支付系统等都需要接收消息。这个时候,单个队列就满足不了需求,一个可行的解决方式是,为每个消费者创建一个单独的队列,让生产者发送多份。

显然这是个比较蠢的做法,同样的一份消息数据被复制到多个队列中会浪费资源,更重要的是,生产者必须知道有多少个消费者。为每个消费者单独发送一份消息,这实际上违背了消息队列“解耦”这个设计初衷。

为了解决这个问题,演化出了另外一种消息模型:“发布 - 订阅模型(Publish-Subscribe Pattern)”。

 

在发布 - 订阅模型中,消息的发送方称为发布者(Publisher),消息的接收方称为订阅者(Subscriber),服务端存放消息的容器称为主题(Topic)。发布者将消息发送到主题中,订阅者在接收消息之前需要先“订阅主题”。“订阅”在这里既是一个动作,同时还可以认为是主题在消费时的一个逻辑副本,每份订阅中,订阅者都可以接收到主题的所有消息。

在消息领域的历史上很长的一段时间,队列模式和发布 - 订阅模式是并存的,有些消息队列同时支持这两种消息模型,比如 ActiveMQ。我们仔细对比一下这两种模型,生产者就是发布者,消费者就是订阅者,队列就是主题,并没有本质的区别。它们最大的区别其实就是,一份消息数据能不能被消费多次的问题。

实际上,在这种发布 - 订阅模型中,如果只有一个订阅者,那它和队列模型就基本是一样的了。也就是说,发布 - 订阅模型在功能层面上是可以兼容队列模型的。

现代的消息队列产品使用的消息模型大多是这种发布 - 订阅模型,当然也有例外。

RabbitMQ 的消息模型

这个例外就是 RabbitMQ,它是少数依然坚持使用队列模型的产品之一。那它是怎么解决多个消费者的问题呢?你还记得我在上节课中讲到 RabbitMQ 的一个特色 Exchange 模块吗?在 RabbitMQ 中,Exchange 位于生产者和队列之间,生产者并不关心将消息发送给哪个队列,而是将消息发送给 Exchange,由 Exchange 上配置的策略来决定将消息投递到哪些队列中。

同一份消息如果需要被多个消费者来消费,需要配置 Exchange 将消息发送到多个队列,每个队列中都存放一份完整的消息数据,可以为一个消费者提供消费服务。这也可以变相地实现新发布 - 订阅模型中,“一份消息数据可以被多个订阅者来多次消费”这样的功能。具体的配置你可以参考 RabbitMQ 官方教程,其中一个章节专门是讲如何实现发布订阅的。

RocketMQ 的消息模型

讲完了 RabbitMQ 的消息模型,我们再来看看 RocketMQ。RocketMQ 使用的消息模型是标准的发布 - 订阅模型,在 RocketMQ 的术语表中,生产者、消费者和主题与我在上面讲的发布 - 订阅模型中的概念是完全一样的。

但是,在 RocketMQ 也有队列(Queue)这个概念,并且队列在 RocketMQ 中是一个非常重要的概念,那队列在 RocketMQ 中的作用是什么呢?这就要从消息队列的消费机制说起。

几乎所有的消息队列产品都使用一种非常朴素的“请求 - 确认”机制,确保消息不会在传递过程中由于网络或服务器故障丢失。具体的做法也非常简单。在生产端,生产者先将消息发送给服务端,也就是 Broker,服务端在收到消息并将消息写入主题或者队列中后,会给生产者发送确认的响应。

如果生产者没有收到服务端的确认或者收到失败的响应,则会重新发送消息;在消费端,消费者在收到消息并完成自己的消费业务逻辑(比如,将数据保存到数据库中)后,也会给服务端发送消费成功的确认,服务端只有收到消费确认后,才认为一条消息被成功消费,否则它会给消费者重新发送这条消息,直到收到对应的消费成功确认。

这个确认机制很好地保证了消息传递过程中的可靠性,但是,引入这个机制在消费端带来了一个不小的问题。什么问题呢?为了确保消息的有序性,在某一条消息被成功消费之前,下一条消息是不能被消费的,否则就会出现消息空洞,违背了有序性这个原则。

也就是说,每个主题在任意时刻,至多只能有一个消费者实例在进行消费,那就没法通过水平扩展消费者的数量来提升消费端总体的消费性能。为了解决这个问题,RocketMQ 在主题下面增加了队列的概念。

**每个主题包含多个队列,通过多个队列来实现多实例并行生产和消费。**需要注意的是,RocketMQ 只在队列上保证消息的有序性,主题层面是无法保证消息的严格顺序的。

RocketMQ 中,订阅者的概念是通过消费组(Consumer Group)来体现的。每个消费组都消费主题中一份完整的消息,不同消费组之间消费进度彼此不受影响,也就是说,一条消息被 Consumer Group1 消费过,也会再给 Consumer Group2 消费。

消费组中包含多个消费者,同一个组内的消费者是竞争消费的关系,每个消费者负责消费组内的一部分消息。如果一条消息被消费者 Consumer1 消费了,那同组的其他消费者就不会再收到这条消息。

在 Topic 的消费过程中,由于消息需要被不同的组进行多次消费,所以消费完的消息并不会立即被删除,这就需要 RocketMQ 为每个消费组在每个队列上维护一个消费位置(Consumer Offset),这个位置之前的消息都被消费过,之后的消息都没有被消费过,每成功消费一条消息,消费位置就加一。这个消费位置是非常重要的概念,我们在使用消息队列的时候,丢消息的原因大多是由于消费位置处理不当导致的。

RocketMQ 的消息模型中,比较关键的概念就是这些了。为了便于你理解,我画了下面这张图:

 

你可以对照这张图再把我刚刚讲的这些概念继续消化一下,加深理解。

Kafka 的消息模型

我们再来看看另一种常见的消息队列 Kafka,Kafka 的消息模型和 RocketMQ 是完全一样的,我刚刚讲的所有 RocketMQ 中对应的概念,和生产消费过程中的确认机制,都完全适用于 Kafka。唯一的区别是,在 Kafka 中,队列这个概念的名称不一样,Kafka 中对应的名称是“分区(Partition)”,含义和功能是没有任何区别的。

小结

我们来总结一下本节课学习的内容。首先我们讲了队列和主题的区别,这两个概念的背后实际上对应着两种不同的消息模型:队列模型和发布 - 订阅模型。然后你需要理解,这两种消息模型其实并没有本质上的区别,都可以通过一些扩展或者变化来互相替代。

常用的消息队列中,RabbitMQ 采用的是队列模型,但是它一样可以实现发布 - 订阅的功能。RocketMQ 和 Kafka 采用的是发布 - 订阅模型,并且二者的消息模型是基本一致的。

最后提醒你一点,我这节课讲的消息模型和相关的概念是业务层面的模型,深刻理解业务模型有助于你用最佳的姿势去使用消息队列。

但业务模型不等于就是实现层面的模型。比如说 MySQL 和 Hbase 同样是支持 SQL 的数据库,它们的业务模型中,存放数据的单元都是“表”,但是在实现层面,没有哪个数据库是以二维表的方式去存储数据的,MySQL 使用 B+ 树来存储数据,而 HBase 使用的是 KV 的结构来存储。同样,像 Kafka 和 RocketMQ 的业务模型基本是一样的,并不是说他们的实现就是一样的,实际上这两个消息队列的实现是完全不同的。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

《消息队列高手课》主题和队列有什么区别? 的相关文章

  • java.lang.NoClassDefFoundError:org.apache.batik.dom.svg.SVGDOMImplementation

    我在链接到我的 Android LibGDX 项目的 Apache Batik 库时遇到了奇怪的问题 但让我们从头开始 在 IntelliJ Idea 中我有一个项目 其中包含三个模块 Main Android 和 Desktop 我强调的
  • Java new Date() 打印

    刚刚学习 Java 我知道这可能听起来很愚蠢 但我不得不问 System out print new Date 我知道参数中的任何内容都会转换为字符串 最终值是 new Date 返回对 Date 对象的引用 那么它是如何打印这个的呢 Mo
  • 在画布上绘图

    我正在编写一个 Android 应用程序 它可以在视图的 onDraw 事件上直接绘制到画布上 我正在绘制一些涉及单独绘制每个像素的东西 为此我使用类似的东西 for int x 0 x lt xMax x for int y 0 y lt
  • Play框架运行应用程序问题

    每当我尝试运行使用以下命令创建的新 Web 应用程序时 我都会收到以下错误Play http www playframework org Error occurred during initialization of VM Could no
  • 在 HTTPResponse Android 中跟踪重定向

    我需要遵循 HTTPost 给我的重定向 当我发出 HTTP post 并尝试读取响应时 我得到重定向页面 html 我怎样才能解决这个问题 代码 public void parseDoc final HttpParams params n
  • JAXb、Hibernate 和 beans

    目前我正在开发一个使用 Spring Web 服务 hibernate 和 JAXb 的项目 1 我已经使用IDE hibernate代码生成 生成了hibernate bean 2 另外 我已经使用maven编译器生成了jaxb bean
  • INSERT..RETURNING 在 JOOQ 中不起作用

    我有一个 MariaDB 数据库 我正在尝试在表中插入一行users 它有一个生成的id我想在插入后得到它 我见过this http www jooq org doc 3 8 manual sql building sql statemen
  • 列出jshell中所有活动的方法

    是否有任何命令可以打印当前 jshell 会话中所有新创建的方法 类似的东西 list但仅适用于方法 您正在寻找命令 methods all 它会打印所有方法 包括启动 JShell 时添加的方法 以及失败 被覆盖或删除的方法 对于您声明的
  • JavaMail 只获取新邮件

    我想知道是否有一种方法可以在javamail中只获取新消息 例如 在初始加载时 获取收件箱中的所有消息并存储它们 然后 每当应用程序再次加载时 仅获取新消息 而不是再次重新加载它们 javamail 可以做到这一点吗 它是如何工作的 一些背
  • 禁止的软件包名称:java

    我尝试从数据库名称为 jaane 用户名 Hello 和密码 hello 获取数据 错误 java lang SecurityException Prohibited package name java at java lang Class
  • 如何为俚语和表情符号构建正则表达式 (regex)

    我需要构建一个正则表达式来匹配俚语 即 lol lmao imo 等 和表情符号 即 P 等 我按照以下示例进行操作http www coderanch com t 497238 java java Regular Expression D
  • JRE 系统库 [WebSphere v6.1 JRE](未绑定)

    将项目导入 Eclipse 后 我的构建路径中出现以下错误 JRE System Library WebSphere v6 1 JRE unbound 谁知道怎么修它 右键单击项目 特性 gt Java 构建路径 gt 图书馆 gt JRE
  • AWS 无法从 START_OBJECT 中反序列化 java.lang.String 实例

    我创建了一个 Lambda 函数 我想在 API 网关的帮助下通过 URL 访问它 我已经把一切都设置好了 我还创建了一个application jsonAPI Gateway 中的正文映射模板如下所示 input input params
  • 如何从泛型类调用静态方法?

    我有一个包含静态创建方法的类 public class TestClass public static
  • 捕获的图像分辨率太大

    我在做什么 我允许用户捕获图像 将其存储到 SD 卡中并上传到服务器 但捕获图像的分辨率为宽度 4608 像素和高度 2592 像素 现在我想要什么 如何在不影响质量的情况下获得小分辨率图像 例如我可以获取或设置捕获的图像分辨率为原始图像分
  • 使用 JMF 创建 RTP 流时出现问题

    我正处于一个项目的早期阶段 需要使用 RTP 广播DataStream创建自MediaLocation 我正在遵循一些示例代码 该代码目前在rptManager initalize localAddress 出现错误 无法打开本地数据端口
  • java.lang.IllegalStateException:驱动程序可执行文件的路径必须由 webdriver.chrome.driver 系统属性设置 - Similiar 不回答

    尝试学习 Selenium 我打开了类似的问题 但似乎没有任何帮助 我的代码 package seleniumPractice import org openqa selenium WebDriver import org openqa s
  • 将 List 转换为 JSON

    Hi guys 有人可以帮助我 如何将我的 HQL 查询结果转换为带有对象列表的 JSON 并通过休息服务获取它 这是我的服务方法 它返回查询结果列表 Override public List
  • 节拍匹配算法

    我最近开始尝试创建一个移动应用程序 iOS Android 它将自动击败比赛 http en wikipedia org wiki Beatmatching http en wikipedia org wiki Beatmatching 两
  • 使用 xpath 和 vtd-xml 以字符串形式获取元素的子节点和文本

    这是我的 XML 的一部分

随机推荐

  • 在vue项目中使用高德地图

    1 安装高德地图插件 npm install vue amap save 2 申请高德地图账号和key 官网地址 高德开放平台 高德地图API 3 在main js中引入 引入vue amap import VueAMap from vue
  • 三维包围盒碰撞检测算法-Python(OBB-SAT)

    想实现一个检测三维包围盒是否发生碰撞的功能 因为目标是任意方向的三维包围盒 即没有和坐标轴对齐的旋转包围盒 所以考虑采用检测OBB碰撞的SAT算法 但找了很久没找到现成的python代码 就比着别人写的C 版本自己写了python的代码 1
  • CAT3、CAT4、CAT5、CAT5E、CAT6、CAT6A、CAT7和CAT8网线的介绍

    目录 1 CAT3网线 10Mbps 2 CAT4网线已淘汰 3 CAT5 网线 100MHz 100Mbps 4 CAT5E网线 100MHz 1000Mbps 5 CAT6网线 250MHz 1000Mbps 6 CAT6A 网线 50
  • C#程序中进行FTP上传下载时出现的问题

    在C 程序中 进行ftp操作时容易出现的问题 The remote server returned an error 550 File unavailable e g file not found no access 解决方法 首先 查看登
  • matlab中的twomodegauss函数-双峰高斯函数

    文章搬运于 http blog sina com cn s blog 4fc818ea0101l8kn html function p twomodegauss m1 sig1 m2 sig2 A1 A2 k TWOMODEGAUSS Ge
  • STM32在FREEOS进行IAP跳转死机

    现象 STM32使用串口IAP进行跳转 发现APP程序用freeos编写的时候 程序跳转完成后就死机了 IAP程序在跳转前都关闭了中断和复位了使用的外设 已经验证跳转到裸机程序没有问题 FREEOS的程序在main函数开始就进行了开启中断和
  • wireshark取证案例学习笔记

    此文对应wireshark取证分析练习题前5道 题目来源 及PACP包下载地址 自己学习的一点笔记和心得 记录下来以免遗忘 练习题1的任务书解答 某公司怀疑其雇员张小花是其竞争对手派来的商业间谍 张小花访问了公司的一个机密配方 安保人员担心
  • Windows删除本地svn项目文件夹

    在window下 打开DOS命令窗口 进入需要清除svn的文件目录 输入如下命令 for r a in do if exist a svn rd s q a svn
  • ecs云服务器网站迁移,ecs云服务器网站迁移

    ecs云服务器网站迁移 内容精选 换一换 备案是中国大陆的一项法规 使用大陆节点服务器提供互联网信息服务的用户 需要在服务器提供商处提交备案申请 根据工信部 互联网信息服务管理办法 国务院292号令 和工信部令第33号 非经营性互联网信息服
  • GNU协议条款

    感谢原文作者 http v266 yo2 cn articles gnu E7 99 BE E7 A7 91 E5 90 8D E8 AF 8D E8 A7 A3 E9 87 8A html GNU 包含3个协议条款 GPL GNU通用公共
  • 最简单的DRM应用程序 (page-flip)

    在上一篇 最简单的DRM应用程序 double buffer 中 我们了解了DRM更新图像的一个重要接口drmModeSetCrtc 在本篇文章中 我们将一起来学习DRM另一个重要的刷图接口 drmModePageFlip drmModeP
  • linux stl内存池的应用

    在stl里面 stl对容器中元素内存的分配 与元素的构造 析构过程进行了分离 c 在全局定义了 operator new size t size 调用malloc size operator new size t size void buf
  • Vue 中发送请求时防止按钮多次点击

  • 【React + Umi】自定义离开页面拦截弹框事件

    在 react umi 中对离开页面的行为进行自定义弹窗拦截控制 以下为可选的方案分析 wrapper 首先 因为项目框架是 umi 最先想到了 umi 路由的 wrapper 装饰器 但仔细一想又不太对 wrapper 争对于跳转到某个特
  • Redis是用C语言开发的一个开源的高性能键值对(key-value)数据库

    字符串类型 string redis的字符串是二进制安全的 什么是二进制安全 简单理解就是存入什么数据取出的还是什么数据 商品编号 订单号采用string的递增数字特性生成 散列类型 hash hash叫散列类型 它提供了字段和字段值的映射
  • VS2008中的ncb、pdb文件分析

    NCB是 No Compile Browser 的缩写 称为 无编译浏览文件 其中存放了供ClassView WizardBar和Component Gallery使用的信息 由VC开发环境自动生成 工程拷来拷去都会生成新的信息以适应新的环
  • 【python】CodingGames Shadows of the Knight - Episode 1

    CodingGames 之 蝙蝠侠救人 描述 你是蝙蝠侠 你将通过使用你的抓斗枪从一个窗口跳到另一个窗口来寻找给定建筑物上的人质 您的目标是跳到人质所在的窗口以解除炸弹的武装 不幸的是 在炸弹爆炸之前你的跳跃次数是有限的 在每次跳跃之前 热
  • 经验:在Maven项目中,打包时指定 main class 的配置(亲测有效)

    Maven打包的时候 是不是经常会出现 没有主清单属性 的报错 以下的配置 放在 pom xml 最后的 lt build gt 中 就能自定义main函数所在的类 然后打包了
  • 谷歌、OpenAI等警告:BERT、GPT-3等大型语言模型都有一个重大缺陷,很危险...

    2020 12 17 00 14 54 作者 青暮 语言模型已经变得越来越强大 可胜任的任务也越来越多 这些仅仅以预测句子中下一个单词进行训练的模型 已经在诸如问答 翻译等应用程序中取得了突破性的进展 前段时间在社交媒体上活跃异常的GPT
  • 《消息队列高手课》主题和队列有什么区别?

    如果你研究过超过一种消息队列产品 你可能已经发现 每种消息队列都有自己的一套消息模型 像队列 Queue 主题 Topic 或是分区 Partition 这些名词概念 在每个消息队列模型中都会涉及一些 含义还不太一样 为什么出现这种情况呢