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

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

你好,我们又见面了。

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

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

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

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

九、顺序消息

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

什么是顺序消息?

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

典型业务场景

场景 为什么需要顺序
订单状态流转 创建 → 支付 → 发货 → 完成,必须按顺序执行,绝不能先完成再创建
库存扣减日志 同一商品的入库和出库操作,必须按时间顺序处理,否则库存数据会错乱
数据库 binlog 同步 数据库变更日志必须按顺序回放,否则数据不一致
IoT 设备指令 对同一设备的控制指令必须有序执行(如开 → 关 → 开)

flowchart LR subgraph Wrong❌ 乱序消费 W1创建订单 --> W3发货 W2支付 --> W3 W1 --> W2 NoteWrong"支付可能在创建之前被消费\→ 数据错乱" end subgraph Right✅ 顺序消费 R1创建订单 --> R2支付 --> R3发货 --> R4完成 NoteRight"严格按照业务顺序执行\→ 数据正确" 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 --> C1Consumer\串行消费 NoteGlobal"全局有序 = 1 个 Topic 只有 1 个 Queue\性能瓶颈明显,一般不建议"

分区顺序消息

  • Topic 下有多个 Queue,但同一个业务 Key 的消息始终落到同一个 Queue
  • 不同 Key 的消息可以在不同 Queue 中并行,互不影响
  • 这是 RocketMQ 的默认推荐方式,既保证了顺序,又保持了并行度

flowchart LR subgraph Keys业务 Key 分组 K1订单 A: 创建→支付→发货 K2订单 B: 创建→支付→发货 K3订单 C: 创建→支付→发货 end subgraph Queues多队列并行 K1 -->|哈希取模| Q1Queue 0\串行消费订单 A K2 -->|哈希取模| Q2Queue 1\串行消费订单 B K3 -->|哈希取模| Q3Queue 2\串行消费订单 C end NotePartition"同一个订单的消息在同一个 Queue 中严格有序\不同订单在不同的 Queue 中并行消费"

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

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

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

java 复制代码
// 顺序消息发送示例
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\如 orderId = 12345 --> S2计算 Key 的哈希值 S2 --> S3对可用 Queue 数量取模 S3 --> S4返回固定的 Queue end subgraph Result结果 R1order_123: Queue 0 R2order_678: Queue 1 R3order_123 再次发送: Queue 0 end Selector --> Result NoteSelector"相同 Key → 相同 Queue\不同 Key → 可能不同 Queue"

关键点

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

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

顺序消息在消费端通过 MessageListenerOrderly 实现,其核心是每个 Queue 由单线程串行消费
flowchart TB subgraph Pull拉取层 PMPullMessageService -->|拉取| Q1Queue 0 PM -->|拉取| Q2Queue 1 PM -->|拉取| Q3Queue 2 end subgraph QueueLock队列锁 Q1 --> Lock1锁定 Queue 0 Q2 --> Lock2锁定 Queue 1 Q3 --> Lock3锁定 Queue 2 end subgraph ThreadPool消费线程池 Lock1 --> T1线程 1\串行处理 Queue 0 的消息 Lock2 --> T2线程 2\串行处理 Queue 1 的消息 Lock3 --> T3线程 3\串行处理 Queue 2 的消息 end NotePool"每个 Queue 在同一时刻只被一个线程处理\不同 Queue 之间并行,互不影响"

核心机制

  1. 队列锁 :Consumer 在拉取消息时,会对 Queue 加锁(rebalanceLock),确保同一个 Queue 在同一时刻只被一个线程处理
  2. 串行消费 :一个 Queue 中的消息按拉取顺序,逐个调用 MessageListenerOrderly 处理
  3. 全局锁:如果某个 Consumer 宕机,Rebalance 会将该 Queue 分配给其他实例,新实例会尝试获取队列锁,确保不会有两个实例同时消费同一个 Queue
java 复制代码
// 顺序消费监听器示例
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\继续下一条 Check -->|否| Pause返回 SUSPEND_CURRENT_QUEUE_A_MOMENT Pause --> Sleep暂停该 Queue 消费\进入等待(默认 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并发消息吞吐量 C1Queue 0 --> T1线程池 --> High"🚀 高吞吐" C2Queue 1 --> T1 C3Queue 2 --> T1 C4Queue 3 --> T1 end subgraph Orderly顺序消息吞吐量 O1Queue 0 --> S1单线程 --> Mid"🐢 中吞吐" O2Queue 1 --> S2单线程 O3Queue 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"❌ 队列选择器用错\→ 顺序乱" P2"❌ Consumer \> Queue\→ 资源浪费" P3"❌ 单条消息耗时过长\→ 整体阻塞" P4"❌ 频繁 Rebalance\→ 顺序中断" end subgraph Fixes解决方案 F1"✅ 用 MessageQueueSelector\按 Key 哈希" F2"✅ Consumer 数 ≤ Queue 数" F3"✅ 优化业务逻辑\异步化长任务" F4"✅ 稳定实例\合理配置 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

发送延迟消息

java 复制代码
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发送阶段 PProducer -->|设置 delayTimeLevel=3| BBroker B -->|写入| CLCommitLog end subgraph Index索引构建 CL -->|ReputMessageService| CQConsumeQueue CQ -->|根据延迟等级| SQueueSCHEDULE_TOPIC_XXXX\Queue 3 end subgraph Schedule调度阶段 SQueue -->|定时扫描| TimerScheduleMessageService\每个等级一个定时线程 Timer -->|到达投递时间| RePush重新写入目标 Topic end subgraph Consume消费阶段 RePush --> TargetCQ目标 Topic 的 ConsumeQueue TargetCQ --> ConsumerConsumer 消费 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 个等级的限制,支持任意时间点的定时投递:

java 复制代码
// 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消息投递时间\任意时间戳 --> T2插入时间轮槽位 T2 --> T3时间轮每秒推进 T3 -->|槽位到期| T4触发投递 NoteWheel"高效调度 O(1) 复杂度\支持海量定时消息" 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):

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

Producer 端开启

java 复制代码
DefaultMQProducer producer = new DefaultMQProducer("producer_group");
producer.setEnableMsgTrace(true);          // 开启轨迹
producer.setMsgTraceTopic("RMQ_SYS_TRACE_TOPIC");

Consumer 端开启

java 复制代码
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生产阶段\发送时间、IP、耗时、状态 D2存储阶段\Broker、CommitLog 位置 D3消费阶段\消费组、消费时间、结果 end Query --> DashboardRocketMQ Dashboard Dashboard --> Display style Dashboard fill:#e3f2fd

在 Dashboard 中查询轨迹

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

API 查询示例

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

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

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

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

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

十二、消息过滤

Tag 标签过滤的原理与使用

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

发送时打 Tag

java 复制代码
Message msg1 = new Message("order_topic", "order_create", "内容".getBytes());
Message msg2 = new Message("order_topic", "order_pay", "内容".getBytes());

消费时按 Tag 过滤

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

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

Tag 过滤的极致性能

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

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

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

发送时携带属性

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

消费时使用 SQL92 表达式

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

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

两种过滤方式的性能对比

对比维度 Tag 过滤 SQL92 过滤
过滤依据 Tag 字符串 用户自定义属性
Broker 开销 极低(仅哈希比对) 较高(解析 SQL + 读取属性)
网络传输 不匹配的消息不传输 不匹配的消息不传输
灵活性 低(只能精确匹配 Tag) 高(复杂条件组合)
支持操作符 `
性能损耗 ~1%-2% ~10%-20%
适用场景 简单的消息分类 复杂的业务规则筛选

flowchart LR subgraph Performance性能对比示意 TagTag 过滤\⚡ 极快 -->|哈希比对| Mem内存操作 SQLSQL92 过滤\🐢 较慢 -->|解析+计算| CPUCPU 密集型 end style Tag fill:#c8e6c9 style SQL fill:#fff3e0

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

RocketMQ 的过滤是 Broker 端过滤,即过滤在 Broker 完成,不匹配的消息不会传输给 Consumer,减少了网络流量。
flowchart LR subgraph BrokerBroker 端职责 B1接收 Consumer 订阅请求\存储过滤表达式 B2从 ConsumeQueue 读取消息\根据 Tag 或 SQL 过滤 B3仅将匹配的消息\返回给 Consumer end subgraph ConsumerConsumer 端职责 C1订阅时指定\Tag 或 SQL 表达式 C2接收 Broker 返回的\已过滤后的消息 C3业务处理\不再二次过滤 end B1 --> B2 --> B3 C1 --> Broker Broker --> C2 --> C3

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

实践场景

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

常见误区
flowchart TB subgraph Misconceptions常见误区 M1"❌ 为每个事件单独创建 Topic\(Topic 泛滥)" M2"❌ 所有业务都用 SQL92 过滤\(浪费性能)" M3"❌ Consumer 端再做一次过滤\(重复劳动,浪费网络)" M4"❌ Tag 中使用复杂字符串\(实际上只存哈希码)" end subgraph Correct正确做法 C1"✅ 同一业务共用 Topic\用 Tag 区分事件" C2"✅ 简单分类用 Tag\复杂条件才用 SQL92" C3"✅ 过滤在 Broker 完成\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. 部署与运维篇 (待续...)
相关推荐
佛祖让我来巡山3 天前
消息中间件进阶:RocketMQ 消息的生产与发送(二)
延迟消息·rocketmq生产者·rocketmq发送端
IT界的老黄牛1 个月前
RocketMQ 4.x 任意秒数延迟消息工程实战:MQ 粗延迟 + Redis 补精度 + MDC 链路透传
redis·rocketmq·事务消息·延迟消息
CodeAmaz7 个月前
RocketMQ 顺序消息实现详解
rocketmq·顺序消息
ZNineSun1 年前
RocketMq如何保证消息的顺序性
rocketmq·顺序消息·三把锁
Java码农也是农1 年前
RabbitMQ使用延迟消息
java·rabbitmq·延迟消息
银氨溶液1 年前
RabbitMQ实现延迟消息发送——实战篇
java·spring boot·分布式·后端·职场和发展·rabbitmq·延迟消息
小小工匠2 年前
Kafka - 消息乱序问题的常见解决方案和实现
分布式·kafka·状态机·顺序消息·消息乱序·前置检测
码农爱java2 年前
Kafka 之顺序消息
spring boot·分布式·微服务·kafka·mq·消息中间件·顺序消息
碳烤小肥羊。。。2 年前
RabbitMQ高级篇,进阶内容
延迟消息·rabblitmq·发送者的可靠性·mq的可靠性·消费者的可靠性