RocketMQ中的顺序消息和乱序消息详解
内容编辑中…
1.背景
顺序消息是消息队列 RocketMQ 提供的一种高级消息类型。
对于一个指定的Topic,消息严格按照先进先出(FIFO)的原则进行消息发布和消费。
即先发送的消息先消费,后发送的消息后消费。
顺序消息在发送、存储和投递的处理过程中,强调多条消息间的先后顺序关系。RocketMQ 顺序消息的顺序关系通过消息组(MessageGroup)判定和识别,发送顺序消息时需要为每条消息设置归属的消息组,相同消息组的多条消息之间遵循先进先出的顺序关系,不同消息组、无消息组的消息之间不涉及顺序性。
2.顺序消息的特性
1、消息消费失败或消费超时,会触发服务端重试逻辑,重试消息属于新的消息,原消息的生命周期已结束;
2、顺序消息消费失败进行消费重试时,为保障消息的顺序性,后续消息不可被消费,必须等待前面的消息消费完成后才能被处理。
3、顺序消息仅支持使用MessageType为FIFO的主题,即顺序消息只能发送至类型为顺序消息的主题中,发送的消息的类型必须和主题的类型一致。
3.顺序消息 VS 普通消息
顺序消息 | 普通消息 | |
---|---|---|
原子性 | 消息之间存在偏序关系 | 消息之间没有关联关系 |
顺序性 | 严格有序 | 大致有序 |
扩展性 | 具备一定的可拓展性 | 可拓展性强 |
吞吐 | 受到消息队列数量限制,容易导致消息积压 | 高 |
并发单元 | 同一MessageGroup的消息 | 单条消息 |
编码要求 | 相对较高 | 低 |
4.应用场景
RocketMQ 的顺序消费(也称为有序消息或顺序消息)在某些业务场景中是至关重要的,特别是在那些对消息处理顺序有严格要求的情况下。以下是几种会用到顺序消费的典型场景:
金融交易:例如,在处理用户的账户余额更新时,所有与该用户账户相关的操作必须按照发生的顺序进行处理。如果先处理了取款消息而后处理存款消息,可能会导致用户的账户余额出现错误。
订单处理系统:在一个电商环境中,从下单、支付到发货等步骤都需要保证按顺序执行。比如,不能在订单未创建之前就进行支付确认,也不能在未支付之前就开始发货。
工作流管理:当业务逻辑涉及多个步骤并且这些步骤之间存在依赖关系时,确保每个任务按照正确的顺序被执行是非常重要的。例如,文档审批流程可能需要依次通过不同级别的审核人员。
实时数据分析和日志处理:对于一些需要根据事件发生的时间序列来进行分析的应用来说,保持数据摄入的顺序性可以简化下游处理逻辑并提高准确性。
库存管理系统:产品入库、出库等操作也需要遵循一定的顺序,以确保库存数量的准确性。
为了实现顺序消费,RocketMQ 提供了两种类型的顺序消息:全局顺序和分区顺序
5.全局顺序和分区顺序
5.1 全局有序
可以为Topic设置一个消息队列,使用一个生产者单线程发送数据,消费者端也使用单线程进行消费。
从而保证消息的全局有序,但是这种方式效率低,一般不使用。
5.2 分区有序
在同一 Topic 下的不同分区(Queue)之间不保证顺序,但在同一分区内的消息则保持严格的顺序。
假设一个Topic分配了两个消息队列,生产者在发送消息的时候,可以对消息设置一个路由ID。
比如想保证一个订单的相关消息有序,那么就使用订单ID当做路由ID。
在发送消息的时候,通过订单ID对消息队列的个数取余,根据取余结果选择消息队列。
这样同一个订单的数据就可以保证发送到一个消息队列中。
消费者端使用MessageListenerOrderly处理有序消息。
这就是RocketMQ的局部有序,保证消息在某个消息队列中有序
在实际应用中,通常会选择分区顺序而非全局顺序,因为后者可能导致性能瓶颈(所有的消息都要经过同一个队列),而前者可以在一定程度上平衡顺序性和吞吐量。
为了确保顺序消费的有效性,RocketMQ 使用了特定的消息发送策略(如基于哈希值选择相同的 MessageQueue)以及特殊的消费者监听器(MessageListenerOrderly),它会在消费端加锁以确保同一队列中的消息被单线程顺序处理。
此外,顺序消息不适合使用广播模式,并且建议不要异步发送这类消息,以免破坏其顺序性。
下面用订单进行分区有序的示例。
5.3 分区有序示例
一个订单的顺序流程是:创建、付款、推送、完成。订单号相同的消息会被先后发送到同一个队列中,消费时,同一个 Orderld 获取到的肯定是同一个队列。
6.顺序消费的条件
需要注意的是 RocketMQ 消息的顺序性分为两部分,生产顺序性和消费顺序性。只有同时满足了生产顺序性和消费顺序性才能达到上述的FIFO效果。
6.1 生产顺序性
在分布式环境下,保证消息的全局顺序性是十分困难的,例如两个 RocketMQ Producer A 与 Producer B,它们在没有沟通的情况下各自向 RocketMQ 服务端发送消息 a 和消息 b,由于分布式系统的限制,我们无法保证 a 和 b 的顺序。
因此业界消息系统通常保证的是分区的顺序性,即保证带有同一属性的消息的顺序,我们将该属性称之为 MessageGroup。如图所示,ProducerA 发送了 MessageGroup 属性为 A 的两条消息 A1,A2 和 MessageGroup 属性为 B 的 B1,B2,而 ProducerB 发送了 MessageGroup 属性为 C 的两条属性 C1,C2。
同时,对于同一 MessageGroup,为了保证其发送顺序的先后性,比较简单的做法是构造一个单线程的场景,即不同的 MessageGroup 由不同的 Producer 负责,并且对于每一个 Producer 而言,顺序消息是同步发送的。同步发送的好处是显而易见的,在客户端得到上一条消息的发送结果后再发送下一条,即能准确保证发送顺序,若使用异步发送或多线程则很难保证这一点。
我们简单总结一下顺序发送
RocketMQ 通过生产者和服务端的协议保障单个生产者串行地发送消息,并按序存储和持久化。如需保证消息生产的顺序性,则必须满足以下条件:
- 单一生产者: 消息生产的顺序性仅支持单一生产者,不同生产者分布在不同的系统&#x