理解消息队列:队列与主题的区别
理解消息队列:队列与主题的区别
如果你研究过多种消息队列产品,可能会发现每种消息队列都有自己的一套消息模型,像队列(Queue)、主题(Topic)或分区(Partition)这些名词概念在不同的消息队列模型中含义各异。这是因为没有统一的标准。尽管曾有国际组织尝试制定过消息相关的标准,如早期的 JMS 和 AMQP,但这些标准的进化跟不上消息队列的演进速度,最终被淘汰了。
那么,什么是队列?什么是主题?它们之间有什么区别?为了彻底理解这些概念,我们需要从消息队列的演进说起。
队列与主题的演进
在互联网架构师圈子中有一句名言:“好的架构不是设计出来的,而是演进出来的。”现代消息队列的模式也是经过十几年的演进而来的。
队列模型
最初的消息队列就是一个严格意义上的队列。在计算机领域,“队列(Queue)”是一种数据结构,定义为先进先出(FIFO, First-In-First-Out)的线性表。消息入队出队过程中,需要保证这些消息严格有序,按照什么顺序写进队列,就得按照同样的顺序读出来。这里的“读”实际上是出队,也就是从队列中删除这条消息。
在早期的消息队列中,生产者(Producer)发消息是入队操作,消费者(Consumer)收消息是出队操作,服务端存放消息的容器自然称为“队列”。
![](https://static001.geekbang.org/infoq/42/4245cbb2d2bef747595dcecab19eb0d1.png)
如果有多个生产者向同一个队列发送消息,这个队列中可以消费到的消息就是这些生产者生产的所有消息的合集,顺序是生产者发送消息的自然顺序。如果有多个消费者接收同一个队列的消息,这些消费者之间实际上是竞争的关系,每个消费者只能收到队列中的一部分消息,也就是说任何一条消息只能被其中的一个消费者收到。
发布-订阅模型
如果需要将一份消息数据分发给多个消费者,例如风控系统、分析系统、支付系统等都需要接收订单数据,单个队列就无法满足需求。一个可行的解决方案是为每个消费者创建一个单独的队列,让生产者发送多份消息。然而,这种做法会浪费资源,并且违背了消息队列“解耦”的设计初衷。
为了解决这个问题,演化出了发布-订阅模型(Publish-Subscribe Pattern)。
![](https://static001.geekbang.org/infoq/e7/e73ab9483f436806261d04a52f23f7fc.png)
在发布-订阅模型中,消息的发送方称为发布者(Publisher),消息的接收方称为订阅者(Subscriber),服务端存放消息的容器称为主题(Topic)。发布者将消息发送到主题中,订阅者在接收消息之前需要先“订阅主题”。每个订阅者都可以接收到主题的所有消息。
在消息领域的历史上,队列模式和发布-订阅模式是并存的,有些消息队列同时支持这两种消息模型,比如 ActiveMQ。它们最大的区别其实就是,一份消息数据能不能被消费多次的问题。发布-订阅模型在功能层面上可以兼容队列模型。
现代的消息队列产品大多采用发布-订阅模型,但也有例外。
RabbitMQ 的消息模型
RabbitMQ 是少数依然坚持使用队列模型的产品之一。它通过 Exchange 模块解决多个消费者的问题。生产者将消息发送给 Exchange,由 Exchange 上配置的策略决定将消息投递到哪些队列中。
![](https://static001.geekbang.org/infoq/20/20ac40509d6757acf912e0846a1b0ecc.png)
同一份消息如果需要被多个消费者消费,需要配置 Exchange 将消息发送到多个队列,每个队列中都存放一份完整的消息数据。这变相实现了发布-订阅模型中“一份消息数据可以被多个订阅者多次消费”的功能。
RocketMQ 的消息模型
RocketMQ 使用的是标准的发布-订阅模型。RocketMQ 中,生产者、消费者和主题的概念与发布-订阅模型中的概念完全一致。但是,RocketMQ 还引入了队列的概念。
每个主题包含多个队列,通过多个队列实现多实例并行生产和消费。RocketMQ 只在队列上保证消息的有序性,主题层面无法保证消息的严格顺序。订阅者通过消费组(Consumer Group)来体现,每个消费组都消费主题中一份完整的消息。
Kafka 的消息模型
Kafka 的消息模型和 RocketMQ 基本一致,唯一的区别是 Kafka 中队列的概念被称为“分区(Partition)”。分区的含义和功能与 RocketMQ 中的队列完全相同。
小结
我们总结一下今天的内容。队列和主题的区别实际上对应着两种不同的消息模型:队列模型和发布-订阅模型。这两种消息模型并没有本质上的区别,都可以通过一些扩展或变化来互相替代。
RabbitMQ 采用的是队列模型,但可以实现发布-订阅的功能。RocketMQ 和 Kafka 采用的是发布-订阅模型,二者的消息模型基本一致。
理解业务模型有助于你更好地使用消息队列,但业务模型不等于实现层面的模型。比如,MySQL 和 HBase 都支持 SQL,但它们的实现方式完全不同。同样,Kafka 和 RocketMQ 的业务模型相似,但实现完全不同。
希望通过这篇文章,你能更好地理解消息队列的基础概念
评论