前言:特殊消息场景的落地利器
普通消息只能实现基础的异步通信,而在实际业务中,我们经常会遇到定时处理、严格顺序执行的需求。比如订单超时未支付自动关闭、验证码过期失效、物流状态按序更新等场景,普通消息无法满足,这时候就需要用到RocketMQ的延时消息和顺序消息。
本篇基于SpringBoot环境,手把手实现延时消息、顺序消息的开发,讲解底层核心逻辑,落地真实业务场景,同时梳理常见问题和解决方案,让大家快速掌握这两种高频消息类型的用法。
前置准备:SpringBoot+RocketMQ整合环境正常(沿用第5篇项目),本地RocketMQ服务(4.8.0版本)正常运行,Broker开启自动创建Topic权限。
一、延时消息实战
1.1 延时消息核心认知
1. 定义
消息发送后,不会立即被消费者消费,等待指定的延时时间后,才会进入消费队列供消费者拉取,属于定时触发的消息类型。
2. 4.x版本特性
-
不支持自定义延时时间,内置18个延时等级,通过等级映射固定延时时间
-
核心等级速查:1s、5s、10s、30s、1m、2m、3m、4m、5m、6m、7m、8m、9m、10m、20m、30m、1h、2h
-
等级对应数字:1=1s、2=5s、3=10s...以此类推,18=2h
3. 适用场景
-
订单超时未支付,自动取消订单、回退库存
-
短信/验证码过期失效
-
会员到期提醒、待办任务定时推送
1.2 延时消息开发(SpringBoot版)
1. 生产者封装
在第5篇的RocketMQUtil中,新增延时消息发送方法,通过messageDelayLevel参数指定延时等级:
bash
/**
* 发送延时消息
* @param topic 主题
* @param tag 标签
* @param msg 消息内容
* @param delayLevel 延时等级(1-18)
*/
public <T> void sendDelayMsg(String topic, String tag, T msg, int delayLevel) {
String destination = topic + ":" + tag;
try {
// 构建消息对象,设置延时等级
Message<T> message = MessageBuilder.withPayload(msg).build();
rocketMQTemplate.syncSend(destination, message, 3000, delayLevel);
log.info("延时消息发送成功,destination:{},延时等级:{},消息内容:{}", destination, delayLevel, msg);
} catch (Exception e) {
log.error("延时消息发送失败,destination:{}", destination, e);
}
}
2. 测试接口编写
在MsgController中新增延时消息测试接口,模拟订单超时关闭场景(延时30s,对应等级4):
bash
private static final String DELAY_TOPIC = "delay_topic";
private static final String DELAY_TAG = "delay_tag";
/**
* 测试延时消息(30s后消费)
*/
@GetMapping("/delay")
public String testDelaySend() {
// 模拟订单号
String msg = "订单ID:" + System.currentTimeMillis() + ",超时未支付,30s后自动取消";
// 延时等级4=30s
rocketMQUtil.sendDelayMsg(DELAY_TOPIC, DELAY_TAG, msg, 4);
return "延时消息发送成功,等待30s后消费";
}
3. 延时消息消费者
创建延时消息消费者,监听对应Topic,接收延时消息并执行业务逻辑:
bash
/**
* 延时消息消费者
*/
@Slf4j
@Component
@RocketMQMessageListener(
consumerGroup = "delay_consumer_group",
topic = "delay_topic",
selectorExpression = "delay_tag"
)
public class DelayMsgConsumer implements RocketMQListener<String> {
@Override
public void onMessage(String message) {
try {
log.info("延时消息消费成功,收到消息:{}", message);
// 执行业务:取消订单、回退库存
log.info("执行订单超时取消逻辑完成");
} catch (Exception e) {
log.error("延时消息消费失败", e);
throw new RuntimeException("消费失败,触发重试");
}
}
}
二、顺序消息实战
2.1 顺序消息核心认知
1. 定义
严格按照消息发送的顺序进行消费,保证先发送的消息先被消费,后发送的消息后被消费。
2. 两种顺序模式
-
分区顺序(推荐):同一业务ID的消息发送到同一个Queue,单Queue内消息有序,兼顾吞吐量和顺序性
-
全局顺序(极少用):Topic只有一个Queue,所有消息全局有序,吞吐量极低,不适合高并发场景
3. 适用场景
-
订单流程:创建订单→支付成功→发货→签收
-
物流状态更新:揽收→中转→派送→签收
-
数据同步:增量数据按顺序更新
2.2 顺序消息开发(SpringBoot版)
1. 核心原理
生产者通过业务ID哈希取模,将同一业务的消息路由到同一个Queue;消费者采用单线程消费对应Queue,保证顺序执行。
2. 顺序消息生产者
新增顺序消息发送方法,指定业务ID(如订单ID),实现分区路由:
bash
/**
* 发送顺序消息
* @param topic 主题
* @param tag 标签
* @param msg 消息内容
* @param bizId 业务ID(用于路由到同一Queue,保证顺序)
*/
public <T> void sendOrderlyMsg(String topic, String tag, T msg, String bizId) {
String destination = topic + ":" + tag;
try {
// 同步顺序发送,基于业务ID哈希路由
rocketMQTemplate.syncSendOrderly(destination, msg, bizId);
log.info("顺序消息发送成功,destination:{},业务ID:{},消息内容:{}", destination, bizId, msg);
} catch (Exception e) {
log.error("顺序消息发送失败,destination:{}", destination, e);
}
}
3. 测试接口(模拟订单流程)
按顺序发送订单状态消息,同一订单ID保证路由到同一Queue:
bash
private static final String ORDER_TOPIC = "order_topic";
private static final String ORDER_TAG = "order_tag";
/**
* 测试顺序消息(模拟订单流程)
*/
@GetMapping("/orderly")
public String testOrderlySend() {
// 同一订单ID,保证顺序
String orderId = "ORDER_" + System.currentTimeMillis();
// 按顺序发送:创建→支付→发货→签收
rocketMQUtil.sendOrderlyMsg(ORDER_TOPIC, ORDER_TAG, "订单创建:" + orderId, orderId);
rocketMQUtil.sendOrderlyMsg(ORDER_TOPIC, ORDER_TAG, "订单支付:" + orderId, orderId);
rocketMQUtil.sendOrderlyMsg(ORDER_TOPIC, ORDER_TAG, "订单发货:" + orderId, orderId);
rocketMQUtil.sendOrderlyMsg(ORDER_TOPIC, ORDER_TAG, "订单签收:" + orderId, orderId);
return "顺序消息发送成功,按流程消费";
}
4. 顺序消息消费者
配置consumeMode = ConsumeMode.ORDERLY,开启单线程顺序消费:
bash
/**
* 顺序消息消费者
* consumeMode = ConsumeMode.ORDERLY:开启顺序消费
*/
@Slf4j
@Component
@RocketMQMessageListener(
consumerGroup = "orderly_consumer_group",
topic = "order_topic",
selectorExpression = "order_tag",
consumeMode = ConsumeMode.ORDERLY
)
public class OrderlyMsgConsumer implements RocketMQListener<String> {
@Override
public void onMessage(String message) {
try {
log.info("顺序消息消费成功,内容:{}", message);
// 按顺序执行业务逻辑,不可乱序
Thread.sleep(100);
} catch (Exception e) {
log.error("顺序消息消费失败,消息:{}", message, e);
// 顺序消费失败,不可跳过,必须重试
throw new RuntimeException("顺序消费失败,重试");
}
}
}
三、测试验证与结果查看
3.1 延时消息测试
-
启动SpringBoot项目,调用/mq/delay接口
-
观察控制台,30s后消费者才会打印消费日志
-
登录RocketMQ控制台,查看延时消息的生产时间、消费时间差
3.2 顺序消息测试
-
调用/mq/orderly接口
-
控制台按创建→支付→发货→签收的固定顺序打印日志
-
多次测试,同一订单ID的消息始终保持发送顺序,不会乱序
四、常见问题与避坑指南
延时消息避坑
-
4.x版本仅支持固定延时等级,无法自定义秒数,5.x版本支持自定义延时时间
-
延时消息不支持修改/撤回,发送前需确认延时等级
-
延时消息消费失败,会进入延时重试队列,重试间隔遵循延时等级
顺序消息避坑
-
顺序消费必须开启ConsumeMode.ORDERLY,否则为并发消费,会乱序
-
顺序消费失败不能跳过,必须抛出异常重试,否则会导致后续消息阻塞
-
禁止在顺序消费逻辑中做异步处理,否则会打破顺序
-
同一消费者组内,所有消费者必须配置为顺序消费,配置需一致
五、本篇核心总结
延时消息依靠延时等级实现定时触发,适合超时处理、定时任务场景,4.x版本仅支持固定等级
-
顺序消息通过业务ID路由+单线程消费保证有序,优先选用分区顺序,兼顾性能
-
两种消息均需保证消费可靠性,失败抛出异常触发重试,避免数据丢失
-
SpringBoot环境下通过封装方法,可快速落地业务场景,代码可直接复用
下篇预告
入门篇第7篇:《RocketMQ消息消费可靠性:重试机制+死信队列》,详解消费失败后的重试策略、死信队列原理,实现异常消息的闭环处理,杜绝消息丢失和堆积。