面向消息的中间件(简称MOM)在企业发展中越来越重要。本文介绍了消息中间件中的四种消息传递模型,主要介绍了模型的核心特点以及不同模型之间的区别。四种模型分别是:PTP模型Pub/Sub模型Partition模型Transfer模型其中,PTP模型和Pub/Sub模型是在JMS规范中定义的,消息中间件ActiveMQ实现了JMS规范。但是有些消息中间件并没有实现JMS规范,而是自己设计了一套模型。比如Kafka、RocketMQ就采用了Partition模型。另外,业界还有一些其他的消息传递模型,比如Transfer模型,这是作者自己的名字。1.PTP模型Point-to-Point,点对点的通信模型。PTP基于队列(Queue),一个队列可以有多个生产者,和多个消费者。消息服务器根据接收消息的顺序将消息放入队列中。队列中的每条消息只能被一个消费者消费,消费后会从队列中移除。需要注意的是,这里虽然使用了Queue的概念,但是消息并不是先进入队列的,必须先消费。在多个下游消费者的情况下,一些消息中间件,如ActiveMQ,会将队列中的消息分发给不同的消费者进行并行处理,以提高消费能力。这意味着消息在发送时可能是有序的,但在使用时它们会变得无序。为了保证消费的顺序,一些MQ提供了“独占消费者”或“独占消费者”的概念。在这种情况下,只允许一个消费者消费队列中的消息。如果有多个消费者,则选择其中一个。然而,这意味着在处理消息时没有并行性。如果消息很多,就会出现消息积压。为了解决“独占消费者”的性能问题,有些消息中间件采用了分区的概念来解决性能问题,这个我们后面会介绍。2.Pub/Sub模型发布订阅,即发布订阅模型。在Pub/Sub模型中,生产者向一个主题(Topic)发布消息,所有订阅该Topic的下游消费者都可以接收到该消息。如下图所示:一般情况下一条消息只需要消费一次,那么什么情况下所有的消费者都需要消费这条消息呢?最典型的情况就是数据需要缓存在内存中,并且需要实时更新。例如,作者构建了一个违禁词系统来检测用户输入的评论中的违禁词。本禁言系统部署在N台服务器上。为了提高检测性能,每台机器都会将全量禁止词加载到内存中,通过发送MQ消息完成更新词库。由于采用Pub/Sub模型,每台机器的消费者都可以收到这条消息,直接更新内存中的敏感词汇。3.分区模型为了解决PTP模型下通过“专有消费者”消费有序消息带来的性能问题,一些消息中间件,如rocketmq、kafka等,采用了Partition模型,即分区模型,如下图:当producer向topic发送消息时,最终选择其中一个Partition发送。Parition模型中的分区可以理解为PTP模型中的队列。不同的是,PTP模型中的队列存储了所有的消息,每个Partition只存储了部分数据。对于messager来说,此时多了一个consumergroup的概念,Paritition会分配给同一个consumergroup下的consumer之间,每个consumer只消费分配给自己的Paritition。上图展示了不同的消费者可能被分配了不同数量的Parititions。Paritition模式巧妙的结合了PTP模型和Pub/Sub模型:对于PTP模型:一条消息只会被一个消费者消费,Partition模型中的每个分区最终只会被一个消费者消费。对于通过“exclusiveconsumers”来保证全局消费顺序的场景,在Partition模型中,只需要保证创建的Topic只有一个Partition,而这个Partition只会分配其中一个consumer结束。另外,在大多数场景下,我们不需要保证全局秩序。例如,一个订单会产生三个消息,分别是订单创建、订单支付、订单完成。消费的时候,只有按照这个顺序消费才有意义。但是,订单可以并行消费。比如Order1产生的3条消息发送到Partiton1,Order2产生的3条消息发送到Partition2,这样就实现了不同订单之间的并行消费。对于Pub/Sub模型:一条消息可以被所有的下游消费者消费。在Paritition模型中,只需要为每个消费者设置不同的消费者组即可。但是过多的消费群体会给消息中间件的运维带来麻烦。所以有些消息中间件结合了Partition模型和Pub/Sub模型。比如RocketMQ支持为消费组设置消费模式。如果是集群模式,按照上面的描述进行消费。如果是广播模式,按照Pub/Sub模式消费。当然,Partition模型并不是所有的优点。它最大的限制是Partitions的数量是固定的(虽然可以调整),并且只能分配给其中一个消费者。当consumer的数量大于Partition的数量时,这些多出的consumer将无法消费消息。一些消息中间件对此做了优化,比如rocketmq,支持单分区并行消费。即在单个consumer中,同时启动多个线程来消费这个Partition中的数据。当然,前提是消息是无序的。对于有序消息,只能使用一个线程按顺序消费这个Partition中的数据。数据。4.Transfer模型Paritition模型中consumergroups的概念很有用。同一个topic下的消息可以被多个不同的业务方消费,只要使用不同的消费组,不同消费组消费的位置是单独的Record,相互独立。但是,Paritition模型仍然限制消费者的数量不超过分区的数量。于是又出现了另一种消费模型,我称之为Transfer模型,如下图所示:生产者仍然向主题发送消息,可以为一个主题创建多个通道,这里称为通道。与partitions不同的是,每条发送到topic的消息都会被转发到每一个channel,所以每个channel都有这个topic的全量数据。当然,不需要将完整的消息体复制到频道中,只记录消息元数据即可,表示已经在频道中放置了一条。当消费者消费消息时,他们必须指定从哪个渠道消费。当多个消费者消费同一个通道时,每条消息只有一个消费者会到达,类似于PTP模型。其实我们可以认为,消费同一渠道的消费者自动形成了一个消费群体。但是,与Partition模型不同的是,没有分区的概念,因此消费者的数量可以是任意的。事实上,用GO语言编写的NSQ消息中间件就是采用这种模型。当然,这种模型和PTP一样,不能保证消息的顺序,除非采用类似“专用消费者”的概念。
