聊聊消息队列中的基础概念

我们在使用不同的消息队列产品时,会遇到不同的“名词”,今天我们今天来聊一些相关内容,包括队列、主题、分区等。

消息队列进化史

好的架构不是设计出来的,而是不断演进得来的,消息队列的发展历史,也印证了这一点,消息队列的消息模型主要包括两类:

  1. 队列模型
  2. 发布-订阅模型

队列模型

最初的消息队列就是一个严格意义上的队列,它是一个先进先出(FIFO)的线性表,在具体实现时可以采用链表或者数组的形式,队列只允许在后端进行插入操作,在前端进行删除操作。

早期的消息队列就是按照”队列“的数据结构进行设计,其中生产者(Producer)发消息就是入队操作,消费者(Consumer)收消息就是出队操作。

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

如果需要将一份消息发送到多个消费者,要求每个消费者都要收到全量消息,这时单个队列就不能满足需求,可以考虑针对每个消费者都建一个单独的队列,让生产者发送多份,但是这样做,就违背了消息队列实现“服务解耦”的初衷。

发布-订阅模型

发布-订阅模型就是为了解决队列模型中遇到的多个消费者都消费全量信息的问题。

在发布-订阅模型中,

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

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

分布-订阅模型和队列模型相比,生产者就是发布者,消费者就是订阅者,队列就是主题,并没有本质的区别,它们最大的区别在于,一份消息数据能不能被消费多次的问题。

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

常见消息队列产品的设计模型

接下来我们看常用的消息队列产品的队列模型设计。

RabbitMQ

RabbitMQ是少数来在坚持使用队列模型的产品之一,对于多个消费者的问题,它提供了Exchange模块,位于生产者和消费者之间,生产者不关心将消息发给哪个队列,而是将消息发给Exchange,由Exchange上配置的策略来决定将消息队列发送到哪个队列中。

同一份消息如果需要被多个消费者来消费,需要配置Exchange将消息发送到多个队列,每个队列中都存放一份完整的消息数据,可以为一个消费者提供消费服务。

RocketMQ

RocketMQ使用的消息模型是标准的发布-订阅模型。

几乎所有的消息队列都使用一种“请求-确认”机制,确保消息不会在传递过程中由于网络或者服务器故障而丢失,具体做法:

  1. 在生产端,生产者先将消息发送给服务器,服务器在收到消息并将其放入到主题或者队列后,给生产者发送确认的响应。
  2. 在消费端,消费者在收到消息并完成自己的业务逻辑后,给服务端发送消息消费成功的确认。

这个机制保证了消息在传递过程中的可靠性,但是引入这个机制后,为了确保消息的有序消费,在某一条消息被成功消费之前,下一条消息是不能被消费的。即每个主题在任意时刻,至多只能有一个消费者在消费,这也就没有办法水平扩展消费者的数量来提升消费端整体性能。

为了解决这个问题,RocketMQ引入了队列的概念,在每个主题中包含多个队列,通过多个队列来实现多实例并行消费消息。RocketMQ只保证消息在队列上是有序消费的,在主题层面是无法保证的。

RocketMQ的订阅者是通过消费组来体现的,每个消费组都消费主题中的一份完整的消息,不同消费组之间的消费进度是相互隔离的,即一条消息被消费组1消费后,还会被消费组2消费。

消费组中有多个消费者,同一个组内的消费者是竞争的关系,每个消费者负责消费组内的一部分消息。如果一条消息被消费者1消费,那么同一个消费组内的其他消费者没有办法再次消费这条消息。

为了维护多个消费组的不同的消息消费进度,RocketMQ会为每个消费组在每个队列上都维护一个消费位置信息,即Offset。这个位置之前的消息都已经被消费,之后的消息都没有被消费,没成功消费一条消息,消费位置加一。

Kafka

Kafka也是采取发布-订阅模型。

Kafka和RocketMQ相比,消息模型是一样的,区别在于RocketMQ中的“队列”,在Kafka中,被替换成“分区”。

posted @ 2023-03-07 07:25  李潘  阅读(118)  评论(0编辑  收藏  举报