理解RocketMQ顺序消息的全局有序消息和分区有序消息、延迟消息、事务消息

在 RocketMQ 中,“顺序消息”是指消息的发送顺序与消费顺序一致。根据顺序的范围和粒度不同,分为两种:


1. 全局有序消息(Global Order

概念

所有消息按发送顺序依次进入同一个队列(partition),消费者也严格按这个顺序消费。

通俗理解

就像一条生产线,所有商品只能排队进来,消费者一个一个拿,绝不乱序

特征

  • 所有消息进入同一个 queue;
  • 保证整个 topic 级别的顺序;
  • 吞吐量低(单线程消费);

适用场景

  • 银行转账记录、订单状态流转等严格顺序场景;
  • 数量少、顺序要求极高的业务。

在创建Topic时指定Queue的数量。有三种指定方式

  • 在代码中创建Producer时,可以指定其自动创建的Topic的Queue数量
  • 在RocketMQ可视化控制台中手动创建Topic时指定Queue数量
  • 使用mqadmin命令手动创建Topic时指定Queue数量

2. 分区有序消息(Partition Order / 分区顺序

概念

按照某个业务维度(如订单 ID)将消息发送到指定队列,每个队列内部顺序保证,但不同队列之间不保证顺序

通俗理解

就像多条生产线,每条线内有序,每条线管一个订单编号,不同编号可能并行处理。

特征

  • 将消息根据某个规则(如 hash(orderId))分配到不同 queue;
  • 同一个 key 的消息总是进入同一个 queue,因此消费时顺序一致;
  • 适度牺牲全局顺序,换来并发性能

适用场景

  • 订单系统、物流跟踪:只需要每个订单内有序
  • 高频写入,顺序性重要但不要求全局。

如何选择队列:

如何实现Queue的选择?在定义Producer时我们可以指定消息队列选择器,而这个选择器是我们自己实现了MessageQueueSelector接口定义的。

在定义选择器的选择算法时,一般需要使用选择key。这个选择key可以是消息key也可以是其它数据。但无论谁做选择key,都不能重复,都是唯一的。

一般性的选择算法是,让选择key(或其hash值)该Topic所包含的Queue的数量取模,其结果即为选择出的Queue的QueueId

取模算法存在一个问题:不同选择key与Queue数量取模结果可能会是相同的,即不同选择key的
消息可能会出现在相同的Queue,即同一个Consuemr可能会消费到不同选择key的消息。这个问题如何解决?一般性的作法是,从消息中获取到选择key,对其进行判断。若是当前Consumer需
要消费的消息,则直接消费,否则,什么也不做。这种做法要求选择key要能够随着消息一起被
Consumer获取到。此时使用消息key作为选择key是比较好的做法。

以上做法会不会出现如下新的问题呢?不属于那个Consumer的消息被拉取走了,那么应该消费
该消息的Consumer是否还能再消费该消息呢?同一个Queue中的消息不可能被同一个Group中的
不同Consumer同时消费。所以,消费一个Queue的不同选择key的消息的Consumer一定属于不
同的Group。而不同的Group中的Consumer间的消费是相互隔离的,互不影响的。

存疑:继续理解

public class OrderedProducer {
public static void main(String[] args) throws Exception {
DefaultMQProducer producer = new DefaultMQProducer("pg");
producer.setNamesrvAddr("rocketmqOS:9876");
producer.start();
for (int i = 0; i < 100; i++) {
Integer orderId = i;
byte[] body = ("Hi," + i).getBytes();
Message msg = new Message("TopicA", "TagA", body);
SendResult sendResult = producer.send(msg, new
MessageQueueSelector() {
@Override
public MessageQueue select(List<MessageQueue> mqs,
Message msg, Object arg) {
Integer id = (Integer) arg;
int index = id % mqs.size();
return mqs.get(index);
}
}, orderId);
System.out.println(sendResult);
}
producer.shutdown();
}
}

3. 总结对比

类型顺序粒度队列数并发性吞吐量使用场景
全局有序所有消息1极端顺序要求(交易流水)
分区有序每个分区内有序订单系统、用户行为日志等

4. 示例:订单系统

假设有用户订单状态变化:下单 -> 支付 -> 发货 -> 收货

  • 全局有序:所有订单的这些状态都排队来,严格一个顺序处理。
  • 分区有序:每个订单根据 orderId 落到一个队列,这个订单的状态有序,但不同订单并发处理。

5.延迟消息定义

当消息写入到Broker后,在指定的时长后才可被消费处理的消息,称为延时消息
从 RocketMQ 的存储核心结构 —— CommitLog、ConsumeQueue、IndexFile 的角度,来通俗解释 延时消息的整个生命周期,会让你更深入理解它是怎么“绕一圈”实现的。


6.从 RocketMQ 三大核心存储来理解延迟消息实现原理

结构作用类比
CommitLog所有消息的物理写入日志(顺序写,最底层)日志本
ConsumeQueue按 topic + queue 划分的消息逻辑消费索引目录页
IndexFile支持 key 查询消息的 hash 索引文件查字典

7. 延时消息过程全链路分析(结合这三个结构)


步骤 1:Producer 发出延时消息(如延时 10 秒)
Message msg = new Message("myTopic", "tag", "Hello");
msg.setDelayTimeLevel(3); // 10 秒
producer.send(msg);

此时,RocketMQ 不会把消息写到 myTopic 的 CommitLog 区域中,而是:

写入到特殊的 topic:SCHEDULE_TOPIC_XXXX 的 CommitLog 中。


步骤 2:写入 CommitLog(物理存储)

延时消息被写入 CommitLog,如下:

[SCHEDULE_TOPIC_XXXX, queueId = delayLevel - 1]

这个 topic 就是延迟队列的物理存储区,每个 delayLevel 对应一个 queue。

此时:

  • CommitLog 有数据;
  • ConsumeQueue 还没有生成(因为这还不是真正要消费的消息);
  • Index 也没有生成。

步骤 3:ScheduleMessageService 定时轮询处理

RocketMQ broker 后台的调度线程 ScheduleMessageService 每秒执行:

  1. SCHEDULE_TOPIC_XXXX 对应的 queue(比如 queueId = 2)拉取消息;
  2. 检查是否“到点”;
  3. 如果“延迟时间已到”,就会把该消息“克隆出来”,**重新作为一个“普通消息”写入真正的 topic(比如 myTopic)的 CommitLog 中。

步骤 4:转发后再次写入 CommitLog(真正 topic)

这时候,RocketMQ 会做一件关键的事:

  • 将消息重新写入 真正的 topic(如 myTopic 的 CommitLog;
  • 根据 topic + queueId 更新 ConsumeQueue 文件
  • 根据 key 或 msgId 写入 IndexFile(可选)。

所以你可以理解为:

延时消息在 CommitLog 中写了两次,第一次是中转(延时队列),第二次才是投递。


步骤 5:Consumer 读取消息(从 ConsumeQueue 读取)
  • 消费者订阅 myTopic
  • 根据 myTopic + queueId 找到对应的 ConsumeQueue
  • ConsumeQueue 中记录了 offset + size + tagHash;
  • Consumer 根据这些索引,从 CommitLog 取出真正的消息。

8.延时消息在存储结构中的完整生命周期图解

Producer
   |
   | 发送延时消息(setDelayTimeLevel)
   ↓
CommitLog(写入 SCHEDULE_TOPIC_XXXX)   <—— 第一次写入
   ↓
 ScheduleMessageService 每秒轮询检查
   ↓
时间到:
   ↓
CommitLog(写入实际 topic)             <—— 第二次写入
   ↓             ↓
ConsumeQueue     IndexFile              <—— 建立消费与检索索引
   ↓
Consumer 订阅消费

9.每个结构在延时消息中的作用

结构延时消息阶段中作用
CommitLog存储延时消息(第一次),再存真实消息(第二次)
ConsumeQueue只有真正写入真实 topic 后才建立索引(可被消费)
IndexFile可选,存储 key/msgId 用于快速查找(同样延后建立)

10.事务消息

RocketMQ提供了类似X/Open XA的分布式事务功能,通过事务消息能达到分布式事务的最终一致。XA是一种分布式事务解决方案,一种分布式事务处理模式。

事务消息不支持延迟消息!!!
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
消息回查:
在这里插入图片描述

11.理解XA模式2PC

分布式事务:
对于分布式事务,通俗地说就是,一次操作由若干分支操作组成,这些分支操作分属不同应用,分布在不同服务器上。分布式事务需要保证这些分支操作要么全部成功,要么全部失败。

XA 模式是一个分布式事务协议,使用“两阶段提交(2PC)”来确保多个数据库/资源之间的一致性。

由一个“事务管理器”(Transaction Manager,TM)统一调度;
每个参与者是“资源管理器”(Resource Manager,RM),如 MySQL、Oracle、MQ等;

MQ中 半消息机制 暂存消息,不立刻投递。 也就是半事务消息

下面是一个基于 RocketMQ 使用 XA-2PC 模式(两阶段提交) 的例子:


场景设定:电商系统中的“下单 + 扣库存”操作

假设你开发了一个电商系统,用户每次下单时需要:

  1. 创建订单(写入订单数据库);
  2. 扣减库存(写入库存数据库);
  3. 同时,发送一个消息给其他系统(比如发货系统、用户通知等)。

为了保证这三个操作要么全部成功,要么全部失败,你需要使用 分布式事务。RocketMQ 提供了一种 事务消息(基于2PC)机制 来保证一致性。


RocketMQ XA / 2PC 分布式事务的流程图:
🚶 用户点击下单
   |
Producer.sendMessageInTransaction()  // 发起事务消息
   |
   |———[阶段 1]———
   | RocketMQ 暂时“挂起”这条消息(不投递)
   |
   | 执行本地事务(下单 + 扣库存)
   |    ↳ 如果成功,告诉 MQ “可以提交”
   |    ↳ 如果失败,告诉 MQ “请回滚”
   |
   |———[阶段 2]———
   | RocketMQ 根据返回状态:
   |    ↳ commit:将消息发送给消费者
   |    ↳ rollback:消息丢弃

代码逻辑层面:
步骤 1:发送事务消息(暂不投递)
SendResult sendResult = producer.sendMessageInTransaction(msg, orderData);
  • 这时候 RocketMQ 会把消息写入 broker,但不会立即投递给消费者。
  • 进入“半消息”状态。

步骤 2:执行本地事务(例如订单写库、库存减一)
@Override
public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
    try {
        // 写订单数据库
        // 扣库存数据库
        return LocalTransactionState.COMMIT_MESSAGE; // 事务成功
    } catch (Exception e) {
        return LocalTransactionState.ROLLBACK_MESSAGE; // 事务失败
    }
}

步骤 3:RocketMQ 根据返回的状态决定消息是否正式发送
  • 如果你返回了 COMMIT_MESSAGE,RocketMQ 会把这条消息 正式发送给消费者
  • 如果你返回 ROLLBACK_MESSAGE,消息将被丢弃;
  • 如果你返回 UNKNOWN,RocketMQ 过一段时间后会回查你的事务状态(checkLocalTransaction() 方法)。

RocketMQ 实现分布式事务的核心特性
特性描述
半消息机制暂存消息,不立刻投递
本地事务钩子自定义执行本地事务
事务状态回查处理网络抖动或状态未知场景
最终一致性保障提高可用性、牺牲部分实时性

小结
步骤RocketMQ 做的事应用做的事
1发送“半消息”暂存消息
2等你本地事务执行完下单 + 扣库存(或其他操作)
3你告知是成功还是失败MQ 根据你返回状态决定提交或回滚
4可选:MQ 回查事务状态应用实现 check 回调确认状态

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值