RocketMQ 全栈进阶实战指南
欢迎来到 RocketMQ 的进阶世界!本指南专为希望从"小白"进阶到"实战高手"的开发者打造。我们将从核心概念的现实类比出发,带你完成环境搭建、Spring Boot 代码实战,并深入探讨顺序消息、事务消息等生产环境必备的高级特性。
一、核心概念:用"快递物流"看懂 RocketMQ
RocketMQ 的核心架构可以完美映射到一个大型物流快递公司。理解了这个模型,你就理解了消息队列的本质。
1. Topic(主题)
现实类比: 快递分拣中心的"大货架区域",比如"服装区"、"家电区"、"生鲜区"。
技术解释: Topic 是消息的一级分类,用于隔离不同的业务系统。比如订单系统用 ORDER_TOPIC,支付系统用 PAY_TOPIC。它是消息存储和订阅的最小宏观单位。
发送到
Producer
Topic: ORDER_TOPIC
Message Queue 0
Message Queue 1
Message Queue 2
Consumer Group
2. Tag(标签)
现实类比: 货架上具体商品的"细分标签",比如服装区里的"男装"、"女装",家电区里的"冰箱"、"空调"。
技术解释: Tag 是 Topic 内部的细分。它允许消费者在同一个 Topic 下,只拉取自己感兴趣的消息。比如积分系统只关心 ORDER_TOPIC 下 Tag 为 CREATE(下单)的消息,而不关心 CANCEL(取消)的消息。
3. Group(消费者组)
现实类比: 负责派送的"配送小队"。比如"北京海淀配送组"。组里可以有多个快递员(消费者实例)。
技术解释: Group 是一组拥有相同订阅关系(订阅相同的 Topic 和 Tag)的消费者集合。它的核心作用有两个:
- 负载均衡(分摊压力): 组内的多个消费者实例会共同分摊消息。比如有 100 条消息,组内有 2 个消费者,每人处理 50 条。
- 高可用(容错兜底): 如果组内某个消费者宕机(快递员请假),RocketMQ 会自动把它的任务分配给组内其他存活的消费者,确保消息不丢失。
Consumer Group
Broker
Message Queue 0
Message Queue 1
Message Queue 2
Consumer 1
Consumer 2
Consumer 3
Consumer
二、极速环境搭建:Docker 一键部署
相比繁琐的手动配置,使用 Docker 是最快跑起 RocketMQ 的方式。如果你本地已经安装了 Docker,只需以下两步:
注意: 以下命令适用于 Linux / Mac 终端或 Windows 的 PowerShell。
bash
# 1. 启动 NameServer (端口 9876)
docker run -d --name rmqnamesrv -p 9876:9876 apache/rocketmq:latest sh mqnamesrv
# 2. 启动 Broker (自动连接 NameServer,并开启自动创建 Topic)
docker run -d --name rmqbroker --link rmqnamesrv:namesrv -p 10911:10911 -p 10909:10909 apache/rocketmq:latest sh mqbroker -n namesrv:9876 autoCreateTopicEnable=true
启动成功后,你的 RocketMQ 服务端就已经在本地 9876 端口运行了!
三、Spring Boot 代码实战
1. 引入依赖与配置
在 Spring Boot 项目的 pom.xml 中引入 Starter:
xml
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.2.3</version>
</dependency>
在 application.yml 中配置 NameServer 地址:
yaml
rocketmq:
name-server: 127.0.0.1:9876
producer:
group: my-producer-group
2. 生产者:发送带 Tag 的消息
java
@RestController
public class OrderController {
@Autowired
private RocketMQTemplate rocketMQTemplate;
@GetMapping("/createOrder")
public String createOrder() {
// 发送消息:Topic 为 ORDER_TOPIC,Tag 为 CREATE,消息内容为 JSON
rocketMQTemplate.convertAndSend("ORDER_TOPIC:CREATE", "{\"orderId\":1001, \"status\":\"created\"}");
return "订单创建消息已发送!";
}
}
3. 消费者:精准订阅
java
@Component
@RocketMQMessageListener(
topic = "ORDER_TOPIC",
consumerGroup = "order-process-group",
selectorExpression = "CREATE || PAY" // 只监听 CREATE 和 PAY 标签的消息
)
public class OrderConsumer implements RocketMQListener<String> {
@Override
public void onMessage(String message) {
System.out.println("收到订单消息:" + message);
// 执行业务逻辑...
}
}
四、核心原理与高级特性(生产环境必读)
掌握以下三点,你才算真正跨过了 RocketMQ 的门槛。
1. 顺序消息:保证"下单->支付->发货"不乱序
默认情况下,RocketMQ 是并发消费的,消息到达的顺序不保证与发送一致。但在订单场景中,如果"发货"的消息比"下单"先被消费,系统就会出大 Bug。
解决方案: 使用全局顺序消息或分区顺序消息。核心原理是:将同一笔订单的消息,强制发送到同一个 Message Queue(队列)中,并且消费者单线程去拉取这个队列。
java
// 生产者:根据订单ID哈希,保证同一订单的消息进入同一个队列
rocketMQTemplate.syncSendOrderly("ORDER_TOPIC:CREATE", message, String.valueOf(orderId));
// 消费者:设置 consumeMode = ConsumeMode.ORDERLY 开启顺序消费
@RocketMQMessageListener(
topic = "ORDER_TOPIC",
consumerGroup = "orderly-group",
consumeMode = ConsumeMode.ORDERLY
)
2. 事务消息:解决分布式系统的数据一致性
经典场景:A系统扣款成功,但B系统加余额失败。如何保证两边要么都成功,要么都失败?RocketMQ 提供了类似"两阶段提交"的事务消息机制。
事务消息执行流程
DB Consumer Broker Producer DB Consumer Broker Producer alt [本地事务成功] [本地事务失败] 1. 发送Half Message(半消息) 1.1 存储半消息,返回ACK 2. 执行本地事务(如扣款) 2.1 事务提交成功 3. 提交Commit消息 4. 消息投递给消费者 2.1 事务回滚 3. 提交Rollback消息 4. 删除半消息
详细代码实现
生产者端:
java
@Component
public class TransactionProducer {
@Autowired
private RocketMQTemplate rocketMQTemplate;
// 1. 定义事务执行器
@RocketMQTransactionListener
class OrderTransactionListener implements RocketMQLocalTransactionListener {
// 2. 执行本地事务
@Override
public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) {
try {
// 从消息中获取业务参数
String orderId = new String((byte[])arg);
// 执行本地数据库事务(关键步骤)
boolean result = orderService.updateOrderStatus(orderId, "PAID");
if(result) {
return RocketMQLocalTransactionState.COMMIT; // 事务成功
} else {
return RocketMQLocalTransactionState.ROLLBACK; // 事务失败
}
} catch (Exception e) {
// 异常时返回UNKNOWN,触发回查
return RocketMQLocalTransactionState.UNKNOWN;
}
}
// 3. 事务状态回查
@Override
public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
// 从消息中获取业务ID
String orderId = new String(msg.getBody());
// 查询本地事务状态
String status = orderService.getOrderStatus(orderId);
if ("PAID".equals(status)) {
return RocketMQLocalTransactionState.COMMIT;
} else if ("CANCELLED".equals(status)) {
return RocketMQLocalTransactionState.ROLLBACK;
}
return RocketMQLocalTransactionState.UNKNOWN; // 继续回查
}
}
// 4. 发送事务消息
public void sendTransactionMessage(String orderId) {
// 消息体
String content = "{\"orderId\":\"" + orderId + "\", \"amount\":100.00}";
// 发送事务消息
rocketMQTemplate.sendMessageInTransaction(
"PAY_TOPIC",
MessageBuilder.withPayload(content).build(),
orderId // 传递给executeLocalTransaction的参数
);
}
}
关键点说明:
executeLocalTransaction执行本地事务,返回三种状态:COMMIT:提交消息ROLLBACK:回滚消息UNKNOWN:状态未知,触发回查
checkLocalTransaction在状态未知时被调用,用于事务状态回查- 消息体中应包含足够的业务信息,便于回查时确定事务状态
- 本地事务表建议:存储事务ID、状态、重试次数等信息
3. 消息重试与死信队列
如果消费者在处理消息时抛出了异常怎么办?
- 集群模式下的重试: RocketMQ 会自动帮你重试。默认最多重试 16 次,每次重试的间隔时间会逐渐拉长(比如 1秒、5秒、1分钟...)。
- 死信队列(DLQ): 如果重试了 16 次依然失败,RocketMQ 会将这条消息移入"死信队列"(Topic 名称通常为
%DLQ%+ 消费者组名)。你需要人工介入,去死信队列里排查这条"毒消息"为什么一直处理失败。
java
// 消费者中处理重试逻辑
@RocketMQMessageListener(
topic = "ORDER_TOPIC",
consumerGroup = "dlq-group",
consumeThreadMin = 5,
consumeThreadMax = 10
)
@Component
public class DlqConsumer implements RocketMQListener<String> {
@Override
public void onMessage(String message) {
try {
// 业务处理逻辑
processOrder(message);
} catch (Exception e) {
// 获取重试次数(RocketMQ会自动传递)
int retryTimes = ((MessageExt) message).getReconsumeTimes();
if (retryTimes >= 3) {
// 重要:记录日志并跳过,避免阻塞队列
log.error("消息重试3次后仍失败,已移入死信队列: {}", message);
return; // 不抛出异常,避免无限重试
}
throw e; // 抛出异常触发重试
}
}
}
五、生产环境避坑指南
| 避坑点 | 错误做法 | 正确姿势 |
|---|---|---|
| 资源创建 | 每次发消息都 new 一个 Producer。 | Producer 和 Consumer 极其消耗资源,必须做成全局单例,跟随项目启动而创建,项目关闭而销毁。 |
| 幂等性 | 认为 MQ 绝对不会重复发消息。 | 网络抖动可能导致消息重复投递。消费者必须做幂等处理(比如利用数据库唯一键、Redis 记录已处理的订单ID)。 |
| 大消息 | 直接把几 MB 的图片 Base64 塞进消息体。 | RocketMQ 默认限制消息大小为 4MB。大文件应上传到 OSS/MinIO,消息体里只传文件的 URL 地址。 |
| 消费耗时 | 在 onMessage 里处理了十几秒的复杂业务。 | 这会阻塞消费者的拉取线程。建议将复杂业务丢给内部的线程池异步处理,onMessage 尽快返回成功状态。 |
结语: RocketMQ 博大精深,以上涵盖了 80% 的日常开发与面试核心。特别注意事务消息的实现细节:本地事务与消息发送的原子性是关键,务必通过事务回查机制保证最终一致性。
作者 :不会写程序的未来程序员]
原创声明 :本文为原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
本文链接:https://blog.csdn.net/dfsdcvbhjnj/article/details/161119429?spm=1011.2415.3001.5331