消息队列中的概念和消息模型

每种消息队列都有一套自己的消息模型,像队列(Queue)、主题(topic)、分区(partition)这些概念在每个消息队列模型中都会涉及,但含义不太一样。

为什么出现这种情况,因为没有标准!曾经也有组织尝试制定消息的标准,无奈标准的进化跟不上消息队列的演进速度,这些标准实际上已经被废弃了。

消息模型类型

1. 队列模型(queue pattern)

早期的消息队列就是按照“队列”设计的。生产者(producer)发消息就是“入队”操作,消费者(consumer)接收消息就是“出队”也就是删除操作,服务端存放消息的容器称为“队列”。

队列模型存在的问题,如何实现多端消费?

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

为了解决多端消费的问题,演化出了另一种消息模型,发布-订阅模型。

2. 发布-订阅模型(publish-subscribe pattern)

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

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

队列模型和发布-订阅模型最大的区别就是:一份消息数据能不能被消费多次。

RabbitMQ的消息模型

现代消息队列产品大多使用发布-订阅的消息模型,但RabbitMQ是个例外,依然坚持使用队列消息模型!

那RabbitMQ是如何解决多端消费问题的呢?

RabbitMQ中有一个特色模块Exchange,Exchange位于生产者和队列之间,生产者并不关心将消息发送给哪个队列,生产者只是将消息发送给Exchange,由Exchange上配置的策略来决定将消息投递到哪个队列。

具体配置方式参见官网: RabbitMQ Publish/Subscribe

RocketMQ的消息模型

RocketMQ 使用的消息模型是标准的发布-订阅模型,在RocketMQ中除了发布者、订阅者、主题术语外,RocketMQ也有队列的概念,那队列在RocketMQ中的作用是什么呢?

这要从消息队列的消费机制说起,几乎所有的消息队列产品都使用一种非常常用的“请求-确认”机制,确保消息不会在传递过程中因为网络或服务器故障而丢失。具体做法为:在生产端生产者先将消息发送给服务端,也就是Broker,服务端在收到消息并将消息写入主题或队列后,会给生产者发送确认响应。如果生产者没有收到服务端的确认响应或者收到了失败的响应,则会重新发送消息。在消费端,消费者收到消息并完成自己的业务逻辑后,也会给服务端发送消费成功的确认消息,服务端只有收到消费确认后,才认为一条消息被成功消费,否则他会给消费者重新发送这条消息。

确认机制很好的保证了消息传递过程中的可靠性,但是引入确认机制给消费端带来了问题,什么问题呢?为了确保消息的有序性,在某一条消息被成功消费之前,下一条消息是不能被消息的,否则就违背了消息的有序性原则。如果是这样的话,每个主题在任意时刻只能有一个消息者实例在进行消费,也就无法通过水平扩展消费者实例个数来提高消费端整体的消费性能,为了解决这个问题,RocketMQ在主题下面增加了队列的概念。

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

RocketMQ中订阅者的概念是通过消费组(consumer group)来提现的。每个消费组都消费主题中一份完整的数据,不同消费组之间的消费相互不受影响。

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

在topic的消费过程中,由于消息需要被不同的组进行多次消费,所以消费完的消息并不会立即被删除,这就需要RocketMQ为每个消费组在每个队列上维护一个消费位置(consumer offset),这个位置之前的消息都被消费过,这个位置之后的消息都没有被消费过。每成功消费一条消息,消费位置就加1,消费位置非常重要,丢消息的大多原因都是因为消费位置处理不当造成的。

Kafka消息模型

Kafka的消息模型和RocketMQ是完全一样的,唯一的区别是在kafka中队列的名称不一样,kafka中队列对应的名称是分区(Partition),含义和功能是没有任何区别的。

总结:

  1. 如何实现多端消费?

RabbitMQ中通过Exchange模块上配置策略决定将消息发送给哪些队列。

RocketMQ 和 Kafka中通过定义多消费组实现多端消费。

  1. 如何实现多实例的读写?

RocketMQ 在topic中通过队列来实现,Kafka在topic中通过分区来实现。

参考文章:

  1. 李玥 极客时间《消息队列高手课》
  2. 十分钟入门RocketMQ

你可能感兴趣的:(消息队列)