进阶应用篇:顺序消息、延迟消息、轨迹与过滤

系列第四阶段:进阶应用篇

你好,我们又见面了。

经过前面三阶段的学习,你已经掌握了 RocketMQ 的核心架构、存储原理、发送消费机制以及事务消息。可以说,你现在已经是一名合格的 RocketMQ 开发者了。

但“合格”和“精通”之间,还差一个进阶的距离。在实际业务中,你会遇到各种各样的特殊需求:

  • 订单状态流转必须严格按顺序执行,不能乱序
  • 需要定时或延迟发送消息,比如 30 分钟后自动取消未支付订单
  • 消息出了问题,需要快速定位,知道它经历了什么
  • 不同的消费者只需要消费特定类型的消息,减少无效传输

今天这篇文章,我们就来集中攻克这四大进阶技能:顺序消息、延迟消息、消息轨迹、消息过滤。每一个都是生产环境中的高频需求。老规矩,配合流程图,一步一图。

九、顺序消息

顺序消息的概念与业务场景

什么是顺序消息?

顺序消息是指消息的发送顺序和消费顺序保持一致。比如你发了消息 A、B、C,那么 Consumer 收到和处理消息的顺序也一定是 A、B、C。

典型业务场景:

场景 为什么需要顺序
订单状态流转 创建 → 支付 → 发货 → 完成,必须按顺序执行,绝不能先完成再创建
库存扣减日志 同一商品的入库和出库操作,必须按时间顺序处理,否则库存数据会错乱
数据库 binlog 同步 数据库变更日志必须按顺序回放,否则数据不一致
IoT 设备指令 对同一设备的控制指令必须有序执行(如开 → 关 → 开)
flowchart LR subgraph Wrong[❌ 乱序消费] W1[创建订单] --> W3[发货] W2[支付] --> W3 W1 --> W2 NoteWrong["支付可能在创建之前被消费<br>→ 数据错乱"] end subgraph Right[✅ 顺序消费] R1[创建订单] --> R2[支付] --> R3[发货] --> R4[完成] NoteRight["严格按照业务顺序执行<br>→ 数据正确"] end style Wrong fill:#ffcdd2 style Right fill:#c8e6c9

全局顺序消息与分区顺序消息

RocketMQ 的顺序消息分为两个级别:

全局顺序消息

  • 一个 Topic 下只有一个 MessageQueue
  • 所有消息严格按顺序发送和消费
  • 代价:吞吐量极低(单队列串行,无法并行)
flowchart LR P1[消息1] --> Q1[唯一的 Queue] P2[消息2] --> Q1 P3[消息3] --> Q1 Q1 --> C1[Consumer<br>串行消费] NoteGlobal["全局有序 = 1 个 Topic 只有 1 个 Queue<br>性能瓶颈明显,一般不建议"]

分区顺序消息

  • Topic 下有多个 Queue,但同一个业务 Key 的消息始终落到同一个 Queue
  • 不同 Key 的消息可以在不同 Queue 中并行,互不影响
  • 这是 RocketMQ 的默认推荐方式,既保证了顺序,又保持了并行度
flowchart LR subgraph Keys[业务 Key 分组] K1[订单 A: 创建→支付→发货] K2[订单 B: 创建→支付→发货] K3[订单 C: 创建→支付→发货] end subgraph Queues[多队列并行] K1 -->|哈希取模| Q1[Queue 0<br>串行消费订单 A] K2 -->|哈希取模| Q2[Queue 1<br>串行消费订单 B] K3 -->|哈希取模| Q3[Queue 2<br>串行消费订单 C] end NotePartition["同一个订单的消息在同一个 Queue 中严格有序<br>不同订单在不同的 Queue 中并行消费"]

顺序消息的发送原理(队列选择器 MessageQueueSelector)

顺序消息的发送关键在生产者端——必须保证相同业务 Key 的消息被分配到同一个 Queue。

RocketMQ 提供了 MessageQueueSelector 接口,让你自定义选择逻辑:

// 顺序消息发送示例
producer.send(msg, new MessageQueueSelector() {
    @Override
    public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
        // arg 是业务 Key,比如订单 ID
        String orderId = (String) arg;
        // 对订单 ID 取模,确保同一个订单始终落在同一个 Queue
        int index = Math.abs(orderId.hashCode()) % mqs.size();
        return mqs.get(index);
    }
}, orderId);
flowchart TB subgraph Selector[队列选择器工作流程] S1[消息携带业务 Key<br>如 orderId = 12345] --> S2[计算 Key 的哈希值] S2 --> S3[对可用 Queue 数量取模] S3 --> S4[返回固定的 Queue] end subgraph Result[结果] R1[order_123: Queue 0] R2[order_678: Queue 1] R3[order_123 再次发送: Queue 0] end Selector --> Result NoteSelector["相同 Key → 相同 Queue<br>不同 Key → 可能不同 Queue"]

关键点:

  • 如果某个 Broker 宕机,该 Broker 上的 Queue 会从可用列表中移除,此时哈希取模的结果会变化,可能导致顺序被打破。RocketMQ 的故障规避机制在这种情况下会特殊处理,尽量保持顺序,但极端情况下仍可能乱序。所以顺序消息的 Broker 高可用很重要。

顺序消息的消费原理(顺序消费线程模型)

顺序消息在消费端通过 MessageListenerOrderly 实现,其核心是每个 Queue 由单线程串行消费:

flowchart TB subgraph Pull[拉取层] PM[PullMessageService] -->|拉取| Q1[Queue 0] PM -->|拉取| Q2[Queue 1] PM -->|拉取| Q3[Queue 2] end subgraph QueueLock[队列锁] Q1 --> Lock1[锁定 Queue 0] Q2 --> Lock2[锁定 Queue 1] Q3 --> Lock3[锁定 Queue 2] end subgraph ThreadPool[消费线程池] Lock1 --> T1[线程 1<br>串行处理 Queue 0 的消息] Lock2 --> T2[线程 2<br>串行处理 Queue 1 的消息] Lock3 --> T3[线程 3<br>串行处理 Queue 2 的消息] end NotePool["每个 Queue 在同一时刻只被一个线程处理<br>不同 Queue 之间并行,互不影响"]

核心机制:

  1. 队列锁:Consumer 在拉取消息时,会对 Queue 加锁(rebalanceLock),确保同一个 Queue 在同一时刻只被一个线程处理
  2. 串行消费:一个 Queue 中的消息按拉取顺序,逐个调用 MessageListenerOrderly 处理
  3. 全局锁:如果某个 Consumer 宕机,Rebalance 会将该 Queue 分配给其他实例,新实例会尝试获取队列锁,确保不会有两个实例同时消费同一个 Queue
// 顺序消费监听器示例
consumer.registerMessageListener(new MessageListenerOrderly() {
    @Override
    public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {
        // 消息会按顺序被调用,每次一批(通常是一条)
        for (MessageExt msg : msgs) {
            processOrder(msg);  // 业务处理
        }
        return ConsumeOrderlyStatus.SUCCESS;
    }
});

顺序消息的 Rebalance 处理

顺序消息的 Rebalance 比并发消息更复杂,因为涉及到队列锁的交接:

sequenceDiagram participant C1 as Consumer A participant C2 as Consumer B participant B as Broker Note over C1,B: 正常状态:C1 消费 Queue 0,1;C2 消费 Queue 2,3 C2--xB: Consumer B 宕机 B->>C1: 触发 Rebalance(检测到 C2 下线) C1->>C1: 重新分配 Queue C1->>B: 尝试锁定 Queue 2,3 B-->>C1: 锁定成功(Queue 2,3 无锁) Note over C1: C1 开始消费 Queue 2,3<br>但在此之前,C1 已消费完 Queue 0,1 的当前批次 Note over C1: ✅ 整个过程保证了<br>每个 Queue 同一时刻只被一个 Consumer 消费

Rebalance 中的顺序保障:

  1. 先停止后分配:在 Rebalance 过程中,Consumer 会先释放自己持有的队列锁,再重新分配
  2. 锁定机制:新分配的 Queue 需要成功锁定后才能开始消费,避免两个实例同时消费同一个 Queue
  3. 消息重复风险:由于 Offset 提交是异步的,Rebalance 时仍可能重复消费,业务层依然需要幂等

顺序消息的异常与重试机制

顺序消息的重试和并发消息有很大不同:

异常处理:

情况 处理方式
消费失败(业务异常) 返回 SUSPEND_CURRENT_QUEUE_A_MOMENT,暂停当前 Queue 的消费,等待重试
重试次数 ≤ 最大重试次数 消息被重新投递,再次消费
重试次数 > 最大重试次数 投递到死信队列(DLQ),不再消费
flowchart TB Start[消费消息] --> Check{消费成功?} Check -->|是| Done[✅ 返回 SUCCESS<br>继续下一条] Check -->|否| Pause[返回 SUSPEND_CURRENT_QUEUE_A_MOMENT] Pause --> Sleep[暂停该 Queue 消费<br>进入等待(默认 1 秒)] Sleep --> Retry[重新投递该消息] Retry --> Count{重试次数<br>≤ 最大重试次数?} Count -->|是| Start Count -->|否| DLQ[投递到死信队列] DLQ --> Skip[跳过该消息,继续后续消息] style Done fill:#c8e6c9 style DLQ fill:#ffcdd2

顺序消息重试的关键特点:

  • 阻塞后续消息:一条消息消费失败,该 Queue 后面的消息都会被阻塞,直到这条消息被成功消费或进入 DLQ
  • 影响吞吐量:因为阻塞机制,顺序消息的吞吐量对单条消息的处理时间非常敏感
  • 最大重试次数:默认 10 次(并发消费默认 16 次),可通过 consumer.setMaxReconsumeTimes() 调整

顺序消息与并发消息的性能对比

对比维度 并发消息 顺序消息
线程模型 多线程并行 每个 Queue 单线程
吞吐量 高(充分利用 CPU) 中(受限于单线程串行)
延迟 低 中高(单条失败会阻塞后续)
适用场景 大多数业务 需要严格顺序的场景
推荐 Queue 数 Consumer 数量的 2-3 倍 尽量多(提高并行度)
flowchart LR subgraph Concurrent[并发消息吞吐量] C1[Queue 0] --> T1[线程池] --> High["🚀 高吞吐"] C2[Queue 1] --> T1 C3[Queue 2] --> T1 C4[Queue 3] --> T1 end subgraph Orderly[顺序消息吞吐量] O1[Queue 0] --> S1[单线程] --> Mid["🐢 中吞吐"] O2[Queue 1] --> S2[单线程] O3[Queue 2] --> S3[单线程] Note["单条消息处理慢了,整个 Queue 被拖慢"] end style Concurrent fill:#c8e6c9 style Orderly fill:#fff3e0

性能差距量化:

  • 在相同硬件条件下,并发消息的 TPS 通常比顺序消息高 3-5 倍
  • 顺序消息中,单条消息的处理时间直接影响整个 Queue 的吞吐量

顺序消息的常见踩坑与规避

踩坑 后果 规避措施
使用默认的轮询队列选择器 相同业务 Key 的消息落到不同 Queue,顺序被打乱 必须使用 MessageQueueSelector,按 Key 哈希选 Queue
Consumer 数量超过 Queue 数量 部分 Consumer 空闲,浪费资源 Consumer 数 ≤ Queue 数,建议 Consumer 数 = Queue 数
单条消息处理时间过长 整个 Queue 被阻塞,吞吐量骤降 优化业务逻辑,尽量缩短单条消息的处理时间;如果必须长耗时,考虑拆分或异步化
消费异常返回 RECONSUME_LATER 阻塞后续消息,影响效率 谨慎使用重试,尽可能在业务层解决异常,避免依赖重试
Rebalance 频繁触发 顺序可能被短暂打断,消息重复 稳定 Consumer 实例,避免频繁上下线;使用 AllocateMessageQueueAveragely 策略
将顺序消息和并发消息混在同一 Group Rebalance 混乱,顺序无法保证 分离 Group,顺序消息使用独立的 Group
flowchart TB subgraph Pitfalls[常见踩坑] P1["❌ 队列选择器用错<br>→ 顺序乱"] P2["❌ Consumer > Queue<br>→ 资源浪费"] P3["❌ 单条消息耗时过长<br>→ 整体阻塞"] P4["❌ 频繁 Rebalance<br>→ 顺序中断"] end subgraph Fixes[解决方案] F1["✅ 用 MessageQueueSelector<br>按 Key 哈希"] F2["✅ Consumer 数 ≤ Queue 数"] F3["✅ 优化业务逻辑<br>异步化长任务"] F4["✅ 稳定实例<br>合理配置 Rebalance 参数"] end P1 --> F1 P2 --> F2 P3 --> F3 P4 --> F4

十、延迟消息与定时消息

延迟消息的概念与业务场景

延迟消息是生产者发送消息时指定一个延迟时间,消息在到达该时间后才被 Consumer 消费。它和普通消息的区别在于消息不是立即投递,而是“预约”投递。

典型业务场景:

场景 延迟时间 说明
订单超时自动取消 30 分钟 下单后 30 分钟未支付,自动取消订单
定时提醒 1 天 预约明天上午 10 点的提醒
重试调度 1s, 5s, 10s... 消费失败后延迟重试(RocketMQ 内部已实现)
定时任务调度 固定时间 每天凌晨 2 点执行数据统计

RocketMQ 延迟消息的等级机制(18 个等级)

在 RocketMQ 4.x 及 5.x 早期版本中,延迟消息基于预定义等级实现,不支持任意时间延迟:

等级 1 2 3 4 5 6 7 8 9
延迟 1s 5s 10s 30s 1m 2m 3m 4m 5m
等级 10 11 12 13 14 15 16 17 18
延迟 6m 7m 8m 9m 10m 20m 30m 1h 2h

发送延迟消息:

Message msg = new Message("order_topic", "订单内容".getBytes());
// 设置延迟等级为 3,表示延迟 10 秒
msg.setDelayTimeLevel(3);
producer.send(msg);

为什么使用等级机制?

  • 内部通过 SCHEDULE_TOPIC_XXXX 的 18 个 Queue 来分别存储不同延迟等级的消息
  • 后台定时任务 ScheduleMessageService 扫描这些 Queue,到时间后重新投递
  • 等级机制实现简单,性能好,但不够灵活

延迟消息的存储与调度原理

flowchart TB subgraph Send[发送阶段] P[Producer] -->|设置 delayTimeLevel=3| B[Broker] B -->|写入| CL[CommitLog] end subgraph Index[索引构建] CL -->|ReputMessageService| CQ[ConsumeQueue] CQ -->|根据延迟等级| SQueue[SCHEDULE_TOPIC_XXXX<br>Queue 3] end subgraph Schedule[调度阶段] SQueue -->|定时扫描| Timer[ScheduleMessageService<br>每个等级一个定时线程] Timer -->|到达投递时间| RePush[重新写入目标 Topic] end subgraph Consume[消费阶段] RePush --> TargetCQ[目标 Topic 的 ConsumeQueue] TargetCQ --> Consumer[Consumer 消费] end style SQueue fill:#fff3e0 style Timer fill:#ffcdd2 style TargetCQ fill:#c8e6c9

核心调度逻辑:

  1. ScheduleMessageService 为每个延迟等级启动一个定时线程
  2. 定时线程每隔固定时间(默认 1 秒)扫描对应的 Queue
  3. 检查 ConsumeQueue 中的消息,如果当前时间 ≥ 投递时间,则:
    • 从 CommitLog 中读取完整消息
    • 清除延迟属性,重新写入目标 Topic 的 CommitLog 和 ConsumeQueue
    • 这样 Consumer 就能正常消费到这条消息

延迟消息的消息重投与消费

延迟消息的消费重试和普通消息完全一样(基于 %RETRY% 重试队列)。但有一个特殊点:重试不会重置延迟。即消息第一次被消费失败后,进入重试队列的重试延迟是独立的(由重试队列的延迟等级决定),和消息本身的延迟等级无关。

RocketMQ 5.x 的定时消息新特性(基于时间轮的精准定时)

RocketMQ 5.x 引入了精准定时消息,突破了 18 个等级的限制,支持任意时间点的定时投递:

// 5.x 新特性:精准定时消息
Message msg = new Message("order_topic", "内容".getBytes());
// 指定精确的投递时间戳(毫秒)
msg.setDeliverTimeMs(System.currentTimeMillis() + 30 * 60 * 1000);  // 30 分钟后
producer.send(msg);

底层实现变化:

4.x 5.x
18 个固定等级 支持任意时间点
每个等级一个 Queue 基于 Timing Wheel(时间轮) 算法
定时线程扫描 时间轮 + 多级时间轮,高效调度
适合固定延迟场景 适合任意定时场景
flowchart LR subgraph TimerWheel[时间轮定时消息] T1[消息投递时间<br>任意时间戳] --> T2[插入时间轮槽位] T2 --> T3[时间轮每秒推进] T3 -->|槽位到期| T4[触发投递] NoteWheel["高效调度 O(1) 复杂度<br>支持海量定时消息"] end style TimerWheel fill:#e3f2fd

时间轮的优势:

  • 可以处理任意时间点的定时消息,不再受 18 个等级限制
  • 时间轮算法在调度大量定时任务时性能远优于传统的扫描方式
  • 支持取消定时消息(通过消息 ID)

延迟消息的使用限制与最佳实践

使用限制:

限制 说明
❌ 不支持事务消息 延迟消息不能和事务消息组合使用
❌ 不支持批量消息 批量消息不能设置延迟等级
❌ 4.x 只支持 18 个等级 不能自定义延迟时间(5.x 解决了)
❌ 消息投递后不可取消 一旦 Broker 调度投递,无法撤回(5.x 支持取消部分)

最佳实践:

  1. 评估延迟粒度:如果只需要秒/分钟级别的延迟,4.x 的 18 个等级通常够用
  2. 5.x 新项目推荐使用定时消息:更灵活,支持取消
  3. 避免过长的延迟(> 2 小时):长时间延迟消息占用 Broker 存储,建议采用外部调度方案
  4. 监控延迟消息堆积:延迟消息在 SCHEDULE_TOPIC_XXXX 中堆积,需要监控其数量,避免积压过多导致 Broker 内存压力

十一、消息轨迹与消息查询

消息轨迹的作用与配置开启

消息轨迹(Message Trace) 记录了消息从生产到消费的完整链路,是问题排查的利器。

核心作用:

flowchart LR subgraph Trace[消息轨迹能回答的问题] Q1["❓ 这条消息是谁发的?"] Q2["❓ 什么时候发的?"] Q3["❓ 发到了哪个 Broker?"] Q4["❓ 被消费了吗?"] Q5["❓ 谁消费的?"] Q6["❓ 消费成功还是失败?"] end style Trace fill:#e3f2fd

配置开启:

Broker 端配置(broker.conf):

# 开启消息轨迹
traceTopicEnable=true
# 轨迹数据存储的 Topic 名称(默认)
msgTraceTopicName=RMQ_SYS_TRACE_TOPIC

Producer 端开启:

DefaultMQProducer producer = new DefaultMQProducer("producer_group");
producer.setEnableMsgTrace(true);          // 开启轨迹
producer.setMsgTraceTopic("RMQ_SYS_TRACE_TOPIC");

Consumer 端开启:

DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("consumer_group");
consumer.setEnableMsgTrace(true);
consumer.setMsgTraceTopic("RMQ_SYS_TRACE_TOPIC");

消息轨迹的数据采集与存储(traceTopic)

消息轨迹通过 Hook 机制 无侵入地采集数据:

sequenceDiagram participant P as Producer participant H as Trace Hook participant B as Broker participant Trace as RMQ_SYS_TRACE_TOPIC P->>H: 1. 发送消息前触发 before H->>H: 2. 记录发送开始时间、IP 等 P->>B: 3. 发送消息 B-->>P: 4. 返回响应 P->>H: 5. 发送后触发 after H->>H: 6. 记录耗时、结果 H->>Trace: 7. 将轨迹数据作为普通消息<br>异步发送到 traceTopic

轨迹数据内容:

字段 说明
traceId 全局唯一追踪 ID
messageId 消息 ID
key 业务 Key
topic Topic 名称
producerGroup 生产者组
consumerGroup 消费者组
sendTime 发送时间
consumeTime 消费时间
sendCost 发送耗时
consumeCost 消费耗时
clientIp 客户端 IP
status 状态(成功/失败)
errorMsg 错误信息

消息轨迹的查询与可视化

RocketMQ 提供了 Dashboard 可视化工具来查询消息轨迹:

flowchart LR subgraph Query[查询方式] Q1["按 MessageId 查询"] Q2["按 Key 查询"] Q3["按 Topic + 时间范围查询"] end subgraph Display[展示内容] D1[生产阶段<br>发送时间、IP、耗时、状态] D2[存储阶段<br>Broker、CommitLog 位置] D3[消费阶段<br>消费组、消费时间、结果] end Query --> Dashboard[RocketMQ Dashboard] Dashboard --> Display style Dashboard fill:#e3f2fd

在 Dashboard 中查询轨迹:

  1. 登录 RocketMQ Dashboard
  2. 进入“消息轨迹”页面
  3. 输入 MessageId 或 Key,点击查询
  4. 展示完整的生产 → 存储 → 消费链路

API 查询示例:

// 通过 MessageId 查询轨迹
QueryResult queryResult = admin.queryMessageByMessageId("topic", "messageId");

消息查询的三种方式:按 MessageId、按 Key、按时间

查询方式 原理 适用场景
按 MessageId 直接通过 IndexFile 定位 已知消息 ID,查询单条消息详情
按 Key 通过 IndexFile 哈希索引查询 根据业务 Key(如订单 ID)查询一批消息
按时间 遍历 ConsumeQueue 中的时间戳 查询某段时间内的所有消息,适合批量分析

消息轨迹的日志解读与问题定位

常见问题通过轨迹定位:

flowchart TB Start[问题现象] --> Check{通过轨迹查看} Check -->|消息未收到| A1[查看生产轨迹<br>消息是否成功发送到 Broker?] A1 --> A2{发送成功?} A2 -->|否| A3[Producer 发送失败,检查网络/重试] A2 -->|是| A4[查看消费轨迹<br>是否有 Consumer 订阅?] A4 --> A5{有消费记录?} A5 -->|否| A6[检查消费组是否在线/订阅是否正确] A5 -->|是| A7[查看消费状态<br>是否消费失败?] Check -->|消费失败| B1[查看消费轨迹中的<br>错误信息和耗时] B1 --> B2[定位业务逻辑或下游依赖问题] style A3 fill:#ffcdd2 style A6 fill:#ffcdd2 style A7 fill:#ffcdd2 style B2 fill:#ffcdd2

十二、消息过滤

Tag 标签过滤的原理与使用

Tag 是 Topic 下的二级分类,用于在 Broker 侧对消息进行轻量级过滤。

发送时打 Tag:

Message msg1 = new Message("order_topic", "order_create", "内容".getBytes());
Message msg2 = new Message("order_topic", "order_pay", "内容".getBytes());

消费时按 Tag 过滤:

// 只消费 order_create 和 order_pay 两种 Tag
consumer.subscribe("order_topic", "order_create || order_pay");

Tag 过滤的原理:

flowchart TB subgraph TagFilter[Tag 过滤流程] A[消息携带 Tag] --> B[Broker 构建 ConsumeQueue 时<br>将 Tag 的哈希值存入索引] B --> C[Consumer 订阅时<br>指定 Tag 表达式] C --> D[Broker 拉取消息时<br>根据哈希码快速匹配] D -->|匹配| E[✅ 投递给 Consumer] D -->|不匹配| F[❌ 丢弃,不网络传输] end style E fill:#c8e6c9 style F fill:#ffcdd2

Tag 过滤的极致性能:

  • ConsumeQueue 中存储了 Tag 的 哈希码(8 字节),而非完整 Tag
  • 过滤时只需对比哈希码,无需读取消息体
  • 完全在 Broker 的内存中完成,几乎无性能损耗

SQL92 表达式过滤的原理与使用

当过滤逻辑复杂时(如基于消息属性做条件判断),需要使用 SQL92 过滤。

发送时携带属性:

Message msg = new Message("order_topic", "内容".getBytes());
msg.putUserProperty("amount", "1500");
msg.putUserProperty("region", "SH");
msg.putUserProperty("vip", "true");

消费时使用 SQL92 表达式:

// 只消费上海地区、金额大于 1000、VIP 用户的消息
consumer.subscribe("order_topic", 
    MessageSelector.bySql("region = 'SH' AND amount > 1000 AND vip = true"));

SQL92 过滤的原理:

flowchart TB subgraph SQLFilter[SQL92 过滤流程] A[消息携带用户属性] --> B[Broker 收到拉取请求<br>解析 SQL 表达式] B --> C[读取消息的所有用户属性] C --> D[在 Broker 中执行 SQL 计算] D -->|TRUE| E[✅ 投递给 Consumer] D -->|FALSE| F[❌ 丢弃] end NoteSQL["⚠️ 需要读取完整消息属性<br>消耗更多 CPU 和内存<br>不适合超高吞吐场景"] style E fill:#c8e6c9 style F fill:#ffcdd2

两种过滤方式的性能对比

对比维度 Tag 过滤 SQL92 过滤
过滤依据 Tag 字符串 用户自定义属性
Broker 开销 极低(仅哈希比对) 较高(解析 SQL + 读取属性)
网络传输 不匹配的消息不传输 不匹配的消息不传输
灵活性 低(只能精确匹配 Tag) 高(复杂条件组合)
支持操作符 ||(或) > < = LIKE IN AND OR 等
性能损耗 ~1%-2% ~10%-20%
适用场景 简单的消息分类 复杂的业务规则筛选
flowchart LR subgraph Performance[性能对比示意] Tag[Tag 过滤<br>⚡ 极快] -->|哈希比对| Mem[内存操作] SQL[SQL92 过滤<br>🐢 较慢] -->|解析+计算| CPU[CPU 密集型] end style Tag fill:#c8e6c9 style SQL fill:#fff3e0

消息过滤 Broker 端与 Consumer 端的职责分工

RocketMQ 的过滤是 Broker 端过滤,即过滤在 Broker 完成,不匹配的消息不会传输给 Consumer,减少了网络流量。

flowchart LR subgraph Broker[Broker 端职责] B1[接收 Consumer 订阅请求<br>存储过滤表达式] B2[从 ConsumeQueue 读取消息<br>根据 Tag 或 SQL 过滤] B3[仅将匹配的消息<br>返回给 Consumer] end subgraph Consumer[Consumer 端职责] C1[订阅时指定<br>Tag 或 SQL 表达式] C2[接收 Broker 返回的<br>已过滤后的消息] C3[业务处理<br>不再二次过滤] end B1 --> B2 --> B3 C1 --> Broker Broker --> C2 --> C3

消息过滤的实践场景与误区

实践场景:

场景 推荐方式 理由
同一业务下不同事件(订单创建/支付/退款) Tag 过滤 分类明确,性能最优
同一 Topic 下不同租户的数据 Tag 过滤(租户 ID 作为 Tag) 简单高效
基于金额/地区/用户等级的条件筛选 SQL92 过滤 需要复杂条件
动态变化的过滤条件 SQL92 过滤 灵活调整表达式

常见误区:

flowchart TB subgraph Misconceptions[常见误区] M1["❌ 为每个事件单独创建 Topic<br>(Topic 泛滥)"] M2["❌ 所有业务都用 SQL92 过滤<br>(浪费性能)"] M3["❌ Consumer 端再做一次过滤<br>(重复劳动,浪费网络)"] M4["❌ Tag 中使用复杂字符串<br>(实际上只存哈希码)"] end subgraph Correct[正确做法] C1["✅ 同一业务共用 Topic<br>用 Tag 区分事件"] C2["✅ 简单分类用 Tag<br>复杂条件才用 SQL92"] C3["✅ 过滤在 Broker 完成<br>Consumer 直接处理"] C4["✅ Tag 简洁有意义"] end M1 --> C1 M2 --> C2 M3 --> C3 M4 --> C4

小结

这篇文章涵盖了 RocketMQ 进阶应用的四大模块,通过 10+ 张流程图,我们搞清楚了:

  • 顺序消息:分区顺序的实现原理、队列选择器、顺序消费的线程模型、Rebalance 处理、异常重试以及和并发消息的性能对比
  • 延迟消息与定时消息:4.x 的 18 个延迟等级机制、存储调度原理、5.x 基于时间轮的精准定时消息
  • 消息轨迹:作用、配置开启、轨迹数据的采集存储、查询方式以及通过轨迹定位问题的思路
  • 消息过滤:Tag 过滤和 SQL92 过滤的原理、性能对比、Broker 与 Consumer 的职责分工以及实践场景

到此,你已经掌握了 RocketMQ 从基础到进阶的全部核心知识。从入门认知到架构原理,从存储机制到发送消费,从事务消息到今天的进阶特性,你已经成为了一名能够独立应对各种复杂场景的 RocketMQ 开发者。

恭喜你完成这个系列的学习!希望你能在实际工作中,把今天学到的知识灵活运用,真正让 RocketMQ 成为你手中的利器。


系列文章:

  1. 入门认知篇 ✅
  2. 核心概念与架构篇 ✅
  3. 存储与原理篇(上)✅
  4. 存储与原理篇(中)✅
  5. 存储与原理篇(下)✅
  6. 事务消息 ✅
  7. 进阶应用篇 ✅(本文)
  8. 部署与运维篇 (待续...)
posted @ 2026-07-17 09:15  佛祖让我来巡山  阅读(254)  评论(2)    收藏  举报

佛祖让我来巡山博客站 - 创建于 2018-08-15

开发工程师个人站,内容主要是网站开发方面的技术文章,大部分来自学习或工作,部分来源于网络,希望对大家有所帮助。

Bootstrap中文网