原文出自田守枝的技术博客:https://mp.weixin.qq.com/s?__biz=MzA5MDA5Njk0NQ==&mid=2456618900&idx=1&sn=ebbd4a2f3f8f42f71fa0925b70a9fb1e&utm_source=tuicool&utm_medium=referral
消息中间件( Message Oriented Middleware,简称MOM)在企业开发中变得越来越重要。本文介绍消息中间件中的四种消息投递模型,主要是介绍模型的核心特性,以及不同模型之前的区别。这四种模型分别是:
- PTP模型
- Pub/Sub模型
- Partition模型
- Transfer模型
其中PTP模型和Pub/Sub模型在JMS规范中有定义,消息中间件ActiveMQ就实现了JMS规范。然而一些消息中间件,并没有实现JMS规范,而是自己设计出了一套模型,例如Kafka和RocketMQ就采用了Partition模型。此外业界还有一些其他的消息投递模型,例如Transfer模型,这是笔者自己起的名字。
PTP模型
Point-to-Point,点对点通信模型。PTP是基于队列(Queue)的,一个队列可以有多个生产者,和多个消费者。消息服务器按照收到消息的先后顺序,将消息放到队列中。队列中的每一条消息,只能由一个消费者进行消费,消费之后就会从队列中移除。
需要注意的是,尽管这里使用Queue的概念,但并不是先进入队列消息,一定会被先消费。在存在多个下游Consumer情况下,一些消息中间件,例如ActiveMQ,为了提升消费能力,会将队列中的消息分发到不同Consumer并行进行处理。这意味着消息发送的时候可能是有序的,但是在消费的时候,就变成无序了。为了保证消费的有序,一些MQ提供了"专有消费者”或者"排他消费者”的概念,在这种情况下,队列中的消息仅允许一个消费者进行消费,如果存在多个消费者,那么从中选择一个。但是,这意味着在消息在处理中没有了并行性。如果消息量很多的情况下,将会产生消息积压。为了解决"专有消费者”的性能问题,一些消息中间件采用分区的概念来解决性能问题,我们将在后文进行介绍。
Pub/Sub模型
publish-and- subscribe, 即发布订阅模型。在Pub/Sub模型中,生产者将消息发布到一个主题(Topic)中,订阅了该Topic的所有下游消费者,都可以接收到这条消息。如下图:
通常情况下,一个条消息只要被消费一次就行了,那么什么情况下需要所有的消费者都对这条消息进行消费呢?最典型的情况就是需要在内存中对数据进行缓存,并需要实时进行更新。例如,笔者做过一个违禁词系统,对用户输入的评论内容进行违禁词汇检测。这个违禁词系统,部署了在N台服务器上,为了提升检测性能,每台机器都会将违禁词库全量加载到内存中,词库的更新,是通过发送MQ消息来完成的。由于采用Pub/Sub模型,每台机器的consumer,都可以接收到这条消息,直接在内存中更新敏感词库即可。
Partition模型
为了解决在PTP模型下,有序消息需要通过"专有消费者”消费带来的性能问题,一些消息中间件,如rocketmq,kafka采用了Partition模型,即分区模型,如下所示:
生产者发送消息到某个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中的数据。
Transfer模型
Paritition模型中的消费者组概念很有用,同一个Topic下的消息可以由多个不同业务方进行消费,只要使用不同的消费者组即可,不同消费者组消费到的位置单独记录,互不影响。 但是,Paritition模型还是限制了消费者数量不能多于分区数。因此,又有了另外一种消费模型,笔者称之为Transfer模型,如下图所示:
生产者还是将消息发送到Topic中,针对一个Topic,可以创建多个通道,这里称之为channel。与分区不同的是,发送到Topic中的每条消息,都会转发到每个channel,因此每个channel都有这个Topic的全量数据。当然,没有必要把真的把消息体完整的拷贝一份到channel中,可以只记录一下消息元数据,表示有一条放到这个channel中了。消费者在消费消息时,必须指定从哪个channel消费。多个消费者消费同一个channel时,每条消息只会有一个消费者消费达到,这一点与PTP模型类似。事实上,我们可以认为,消费了同一个channel的消费者,就自动组成了一个消费者组。但是,与Partition模型不同的是,这里没有分区的概念,因此消费者的数量可以是任意的。事实上,GO语言编写的NSQ消息中间件,采用的就是这种模型。当然,这种模型与PTP一样,也不能保证被消息有序,除非通过类似于”专用消费者”的概念。
来源:oschina
链接:https://my.oschina.net/u/817057/blog/3719139