30

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

 4 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzA5MDA5Njk0NQ%3D%3D&%3Bmid=2456618900&%3Bidx=1&%3Bsn=ebbd4a2f3f8f42f71fa0925b70a9fb1e
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

miyU7zZ.png!web

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

  • PTP模型

  • Pub/Sub模型

  • Partition模型

  • Transfer模型

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

1 PTP模型

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

nqYBFre.jpg!web

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

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

为了解决"专有消费者”的性能问题,一些消息中间件采用分区的概念来解决性能问题,我们将在后文进行介绍。

2 Pub/Sub模型

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

NjauUbe.jpg!web

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

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

3 Partition模型

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

6zmeU3z.jpg!web

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

对于消息者,此时多了一个消费者组的概念,Paritition会在同一个消费者组下的消费者中进行分配,每个消费者只消费分配给自己的Paritition。 上图演示了不同的消费者可能会分配到不同数量的 Paritition

Paritition模式巧妙的将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模型,如下图所示:

yQ3ueym.jpg!web

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

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

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

长按二维码,关注我,加好友,进群交流

En6nqub.jpg!web

往期精彩

数据库中间件详解

异地多活场景下的数据同步之道

mysql binlog应用场景与原理深度剖析

InnoDB MVCC 机制

深入理解数据库编程中的超时设置

可靠消息一致性的奇淫技巧

详解HTTP 与TCP中Keep-Alive机制的区别

TCP粘包、拆包与通信协议详解

剖析Spring多数据源


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK