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