消息中间件的四种投递模式对比

雨点打透心脏的1/2处 2022-10-30 13:26 18阅读 0赞

消息中间件的四种投递模式对比

https://cloud.tencent.com/developer/article/1540042

消息中间件( Message Oriented Middleware,简称MOM)在企业开发中变得越来越重要。本文介绍消息中间件中的四种消息投递模型,主要是介绍模型的核心特性,以及不同模型之前的区别。这四种模型分别是:

  • PTP模型
  • Pub/Sub模型
  • Partition模型
  • Transfer模型

其中PTP模型和Pub/Sub模型在JMS规范中有定义,消息中间件ActiveMQ就实现了JMS规范。然而一些消息中间件,并没有实现JMS规范,而是自己设计出了一套模型,例如Kafka和RocketMQ就采用了Partition模型。此外业界还有一些其他的消息投递模型,例如Transfer模型,这是笔者自己起的名字。

image-20210116103144012

1 Pub/Sub 模型

publish- and-subscribe,即发布订阅模型。在 Pub/Sub 模型中,生产者将消息发布到一个 Topic 中,订阅了该 Topic 的所有下游消费者,都会受到这条消息。如下图:

image-20210115145603937
通常情况下,一个条消息只要被消费一次就行了,那么什么情况下需要所有的消费者都对这条消息进行消费呢?最典型的情况就是需要在内存中对数据进行缓存,并需要实时进行更新。

例如,对于一个违禁词系统,对用户输入的评论内容进行违禁词汇检测。这个违禁词系统,部署了在N台服务器上,为了提升检测性能,每台机器都会将违禁词库全量加载到内存中,词库的更新,是通过发送MQ消息来完成的。由于采用Pub/Sub模型,每台机器的consumer,都可以接收到这条消息,直接在内存中更新敏感词库即可。

2 PTP模型

Point-to-Point,点对点通信模型。PTP是基于队列(Queue)的,一个队列可以有多个生产者,和多个消费者。消息服务器按照收到消息的先后顺序,将消息放到队列中。队列中的每一条消息,只能由一个消费者进行消费,消费之后就会从队列中移除。

image-20210115102418932

需要注意的是,尽管这里使用Queue的概念,但并不是先进入队列消息,一定会被先消费。在存在多个下游Consumer情况下,一些消息中间件,例如ActiveMQ,为了提升消费能力,会将队列中的消息分发到不同Consumer并行进行处理。这意味着消息发送的时候可能是有序的,但是在消费的时候,就变成无序了。

为了保证消息的有序性,一些 MQ 提供了 “专有消费者” 或者 “排他消费者” 的概念,在这种情况下,队列中的消息,同一时刻只能由一个消费者在处理消息,如果存在多个消费者,从中选择一个。但是,这意味着消息在处理中没有了并行性,如果消息量很多的情况下,会产生消积压

因此,为了解决 “专有消费者” 的性能问题,一些MQ中间件采用了分区的概念解决性能问题。

3 Partition模型

为了解决在 PTP 模型下,有序消息需要通过“专有消费者”消费带来的性能问题,一些中间件, 如 kafka, rokcetmq 采用了 Partition 模型,即分区模型,如下所示:

image-20210115152849699

4ab6e9c69534656609ca1889bb6f4c03.png

生产者发送消息到某个 Topic 时,最终选择一个 Partition 进行发送。你可以将 Partition 模型中的分区,理解为 PTP 模型中的队列,不同的是, PTP 模型中的队列存储的是所有消息,而 Partitoin 中的只会存储部分数据。

对于消费者,此时多了一个消费者组(consumer group) 的概念,Partition 会在同一个 consumer group 下的消费者中进行分配,每个消费者只能消费分给自己 Partition 中的消息。

上图演示了不同的消费者可能会分配到不同数量的 Partition。

Partition 模式巧妙的讲 PTP 模型和 Pub/Sub 模型结合在了一起

关于 PTP 模型

一条消息只会由一个消费者进行消费,而Partition模型中每个分区最终也只会有一个消费者进行消费。对于通过”专有消费者”来保证全局消费有序的场景,在Partition模型中,只需保证创建的Topic只有一个Partition即可,这个Paritition最终也只会分配其中一个消费者。另外,在绝大部分场景下,我们没有必要保证全局有序,例如一个订单产生了3条消息,分别是订单创建,订单付款,订单完成。消费时,要按照这个顺序消费才能有意义。但是订单之间是可以并行消费的,例如将订单1产生的3条消息发送到Partiton 1,将订单2产生的3条消息发送到Partition 2,如此便达到了不同订单之间的并行消费。

关于Pub/Sub模型

一条消息所有的下游消费者都可以进行消费。在Paritition模型中,只需要为每个消费者设置成不同的消费者组即可。然而,过多的消费者组,会给消息中间件运维带来麻烦。所以一些消息中间件,结合了Partition模型和Pub/Sub模型。例如RocketMQ,支持为消费者组设置消费模式,如果是集群模式,就按照上述描述进行消费,如果是广播模式,就按照Pub/Sub模型进行消费。

当然,Partition模型也不全是优点,其最大的限制在于Partition数量是固定的(虽然可以调整),且只可以分配给其中一个消费者。当消费者的数量大于Partition数量时,这些多出来的消费者将无法消费到消息。

一些消息中间件对此进行了优化,例如rocketmq,支持单个partition的并行消费。即在对单个消费者内,同时启动多个线程,来消费这个Partition中的数据,当然前提是要求消息不是有序的,对于有序的消息,只能使用一个线程按顺序消费这个Partition中的数据。

4 Transfer模型

Paritition模型中的消费者组概念很有用,同一个Topic下的消息可以由多个不同业务方进行消费,只要使用不同的消费者组即可,不同消费者组消费到的位置单独记录,互不影响。 但是,Paritition模型还是限制了消费者数量不能多于分区数。

因此,又有了另外一种消费模型,笔者称之为Transfer模型,如下图所示:

image-20210115154913858

生产者还是将消息发送到Topic中,针对一个Topic,可以创建多个通道,这里称之为channel。与分区不同的是,发送到Topic中的每条消息,都会转发到每个channel,因此每个channel都有这个Topic的全量数据。当然,没有必要把真的把消息体完整的拷贝一份到channel中,可以只记录一下消息元数据,表示有一条放到这个channel中了。

消费者在消费消息时,必须指定从哪个channel消费。多个消费者消费同一个channel时,每条消息只会有一个消费者消费达到,这一点与PTP模型类似。事实上,我们可以认为,消费了同一个channel的消费者,就自动组成了一个消费者组。但是,与Partition模型不同的是,这里没有分区的概念,因此消费者的数量可以是任意的。事实上,GO语言编写的NSQ消息中间件,采用的就是这种模型。

当然,这种模型与PTP一样,也不能保证被消息有序,除非通过类似于”专用消费者”的概念。

发表评论

表情:
评论列表 (有 0 条评论,18人围观)

还没有评论,来说两句吧...

相关阅读

    相关 中间-消息中间

    jms是java ee 的消息规范,其实现是hornetq和activemq等,小型系统直接使用jms是可以的但是大型系统不太适用。 消息中间件主要保证消息的顺序保证,扩展性