消息队列核心-消息模型

目前消息队列有很多种,如kafaka、rocketMq、rabbitMq,所有消息队列的模型都比较类似,基本都包括像队列(Queue)、主题(Topic)或是分区(Partition)等概念。本文详细介绍下消息队列中这些概念的具体含义,文章参考:极客时间-消息队列高手

队列模型

如上图是最早的消息模型,也是最直观的消息模型,我最先理解消息模型的时候,就是这么理解的,消费者发送消息到队列中,生产者从队列中消费消息,如果只有一个生产者、消费者这种模型应该没啥问题。如果存在多个消费者,在这种模型中每个消费者是竞争的关系,每个消息只能被一个消费者消费。

很多场景需要一份消息被多个消费者使用(上游系统的消息,有多个下游系统需要消费),所以出现了发布订阅模型

发布订阅模型

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

发布者将消息发送到主题中,订阅者在接收消息之前需要先“订阅主题”。“订阅”在这里既是一个动作,同时还可以认为是主题在消费时的一个逻辑副本,每份订阅中,订阅者都可以接收到主题的所有消息。

RockteMQ的模型

rocketMQ使用的就是发布订阅模型,如下图是rocketMq的模型图

可以看到rocketMq中有队列(queue)的概念。消息队列中有“请求-确认”的机制,保证消息不丢失(在生产端,生产者先将消息发送给服务端,也就是 Broker,服务端在收到消息并将消息写入主题或者队列中后,会给生产者发送确认的响应。如果生产者没有收到服务端的确认或者收到失败的响应,则会重新发送消息;在消费端,消费者在收到消息并完成自己的消费业务逻辑(比如,将数据保存到数据库中)后,也会给服务端发送消费成功的确认,服务端只有收到消费确认后,才认为一条消息被成功消费,否则它会给消费者重新发送这条消息,直到收到对应的消费成功确认)

这种机制也带来的一些问题:为了保证消息的有序性,在某一条消息被成功消费之前,下一条消息是不能被消费的,否则就会出现消息空洞,违背了有序性这个原则。也就是说,每个主题在任意时刻,至多只能有一个消费者实例在进行消费,那就没法通过水平扩展消费者的数量来提升消费端总体的消费性能。为了解决这个问题,RocketMQ 在主题下面增加了队列的概念

生产者会向所有队列中发送消息(你可以理解为分库分表的模式,某条数据会根据某个规则插入到某个表中),消费的时候可以从多个队列中消费,相当于是并行消费,并行度就是队列的数量

但是这样就无法保证消息消费的有序性了,如果需要保证有序性,可以通过使用顺序主题,或者通生产端保证消息存储到一个队列或分区上就可以保证消息的有序性

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

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

kafka的消息模型

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