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

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

你好,我们又见面了。

经过前面三阶段的学习,你已经掌握了 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界的老黄牛4 个月前
RocketMQ 4.x 任意秒数延迟消息工程实战:MQ 粗延迟 + Redis 补精度 + MDC 链路透传
redis·rocketmq·事务消息·延迟消息
CodeAmaz10 个月前
RocketMQ 顺序消息实现详解
rocketmq·顺序消息
ZNineSun1 年前
RocketMq如何保证消息的顺序性
rocketmq·顺序消息·三把锁
Java码农也是农2 年前
RabbitMQ使用延迟消息
java·rabbitmq·延迟消息
银氨溶液2 年前
RabbitMQ实现延迟消息发送——实战篇
java·spring boot·分布式·后端·职场和发展·rabbitmq·延迟消息
小小工匠2 年前
Kafka - 消息乱序问题的常见解决方案和实现
分布式·kafka·状态机·顺序消息·消息乱序·前置检测
码农爱java2 年前
Kafka 之顺序消息
spring boot·分布式·微服务·kafka·mq·消息中间件·顺序消息
碳烤小肥羊。。。2 年前
RabbitMQ高级篇,进阶内容
延迟消息·rabblitmq·发送者的可靠性·mq的可靠性·消费者的可靠性