前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >消息队列面试解析系列(三)-消息模型辨析

消息队列面试解析系列(三)-消息模型辨析

作者头像
JavaEdge
发布2021-02-22 16:05:48
5950
发布2021-02-22 16:05:48
举报
文章被收录于专栏:JavaEdgeJavaEdge

MQ都得有消息模型,就会产生比如队列(Queue)、主题(Topic)、分区(Partition)这些名词,但是概念上却不尽相同。

因为没有标准。曾经,也有一些国际组织尝试制定消息的标准,比如JMS和AMQP。但标准制定跟不上MQ演进速度,这些标准名存实亡。

队列模型

好的架构不是设计出来的,而是演进出来的。

现代MQ的表现,也是经过十几年演进而来。

最初的消息队列,就是个严格意义的队列。 队列作为一种数据结构,先进先出,即消息入队出队过程,需要保证这些消息严格有序,按什么顺序写进队列,必须按照同样的顺序从队列中读出来。 队列是没有“读”这个操作的,“读”就是出队,也就是从队列中“删除”这条消息。

早期MQ就是按队列数据结构设计。 生产者发消息就是入队; 消费者收消息就是出队,即删除; 服务端存放消息的容器自然就称为“队列”。

  • 最初的消息模型:队列模型

若多生产者往同一队列发消息,这个队列中可以消费到的消息,就是这些生产者生产的所有消息的合集。消息的顺序就是这些生产者发送消息的自然顺序。 若多消费者接收同一队列的消息,这些消费者间就是竞争关系,每个消费者只能收到队列中的一部分消息,即任一消息只能被其中一个消费者收到。

若需将一份消息数据分发给多消费者,要求每个消费者都能收到全量消息。 例如,对一份订单数据,风控系统、分析系统、支付系统等都需要接收消息。这时,单队列就满足不了需求,一个可行的解决方式:为每个消费者创建一个单独队列,让生产者发送多份。 这很蠢,同份消息数据被复制到多队列会浪费资源,而且生产者必须知道有多少消费者。为每个消费者单独发一份消息,这实际上违背了消息队列“解耦”这个设计初衷。

为解决该问题,演化出新的消息模型:

发布-订阅模型(Publish-Subscribe Pattern)

  • 消息发送方称为发布者(Publisher)
  • 消息接收方称为订阅者(Subscriber)
  • 服务端存放消息的容器称为主题(Topic)

发布者将消息发送到主题,订阅者在接收消息前需先“订阅主题”。

“订阅”在这既是一个动作,还可认为是主题在消费时的一个逻辑副本。 每份订阅中,订阅者都可接收到主题的所有消息。

在消息领域的很长段时间,队列模式和发布-订阅模式并存,有些消息队列同时支持这两种消息模型,比如ActiveMQ。 对比起来,生产者就是发布者,消费者就是订阅者,队列就是主题,并无本质区别。 最大区别:一份消息数据能否被消费多次。

发布-订阅模型中,如果只有一个订阅者,那它和队列模型无异。即发布-订阅模型在功能上可兼容队列模型。

现代MQ使用消息模型大多是发布-订阅模型,也有例外,比如兔子MQ:

RabbitMQ消息模型

少数依然坚持使用队列模型的产品之一。怎么解决多消费者问题的? RabbitMQ中,Exchange位于生产者和队列间,生产者并不关心将消息发给哪个队列,而将消息发送给Exchange,由Exchange策略决定将消息投递到哪些队列。

同份消息若需被多消费者消费,需配置Exchange将消息发到多个队列,每个队列都存放一份完整消息数据,可为一个消费者提供消费服务。这也可变相实现发布-订阅模型的“一份消息数据可被多订阅者多次消费”功能。

RocketMQ的消息模型

RocketMQ使用标准的发布-订阅模型,其中的生产者、消费者、主题与前文讲的发布-订阅模型中的概念一致。

RocketMQ还有队列概念,又有何作用? 几乎所有MQ都使用一种非常朴素的“请求-确认”机制,确保消息不会在传递过程中由于网络或服务器故障而丢失。 具体做法也简单。

  • 生产者先将消息发给服务端(即Broker),服务端在收到消息并将消息写入主题或队列后,给生产者发送确认的响应。
    • 若生产者未收到服务端的确认或收到失败的响应,则重新发送;
  • 消费者在收到消息并完成消费业务逻辑(比如将数据保存到数据库)后,也会给服务端发消费成功的确认,服务端只有收到消费确认后,才认为一条消息被成功消费,否则它会给消费者重发送这条消息,直到收到对应的消费成功确认

确认机制保证消息传递过程中的可靠性,但该机制在消费端带来不小问题。 为确保消息的有序性,在某条消息被成功消费前,下条消息是不能被消费的,否则就会出现消息空洞,违背有序性原则。 即每个主题在任意时刻,至多只能有一个消费者实例在进行消费,那就没法通过水平扩展消费者数量提升消费端总体的消费性能。为解决问题,RocketMQ在主题下增加队列概念。

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

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

角色完全相同的消费者被分组在一起,称为消费组。通过它,在消息消费方面实现负载平衡和容错非常容易。

Consumer Group的Consumer实例必须具有完全相同的主题订阅。

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

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

  • RocketMQ的消息模型的核心概念

在消费时,为保证消息的不丢失和严格顺序,每个队列只能串行消费,无法做到并发,否则会出现消费空洞的问题。那如果放宽一下限制,不要求严格顺序,能否做到单个队列的并行消费呢?如果可以,该如何实现? todo

Kafka的消息模型

RocketMQ的完全一致,上节所有RocketMQ中对应的概念,和生产消费过程中的确认机制,都完全适用于Kafka。 唯一区别: Kafka中,队列概念的名称不一样,Kafka中对应的名称分区(Partition),本质毫无差异。

总结

队列和主题的区别,这俩概念的背后实际对应两种不同的消息模型:队列模型和发布-订阅模型。这两种消息模型其实并无本质区别,都可通过一些扩展或者变化来互相替代。

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

本文消息模型相关概念都是业务层面模型,但业务模型不等于实现层面模型。 比如MySQL和Hbase都是支持SQL的数据库,它们的业务模型中,存放数据的单元都是“表”。 但在实现层面,没有哪个数据库是以二维表方式存储数据,MySQL使用B+树,HBase使用KV结构。 同理,像Kafka和RocketMQ的业务模型基本一样,并非指实现就一样,实际俩实现完全不同。

参考

  • 《消息队列高手》
  • http://rocketmq.apache.org/docs/core-concept/
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-07-30 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 队列模型
  • 发布-订阅模型(Publish-Subscribe Pattern)
  • RabbitMQ消息模型
  • RocketMQ的消息模型
  • Kafka的消息模型
  • 总结
相关产品与服务
消息队列 CMQ 版
消息队列 CMQ 版(TDMQ for CMQ,简称 TDMQ CMQ 版)是一款分布式高可用的消息队列服务,它能够提供可靠的,基于消息的异步通信机制,能够将分布式部署的不同应用(或同一应用的不同组件)中的信息传递,存储在可靠有效的 CMQ 队列中,防止消息丢失。TDMQ CMQ 版支持多进程同时读写,收发互不干扰,无需各应用或组件始终处于运行状态。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档