SpringBoot整合RocketMQ:生产者、消费者快速搭建
作者:黒漂技术佬 适用读者:有SpringBoot基础,想快速上手RocketMQ的同学
一、为什么要用SpringBoot整合RocketMQ?
你在搞一套无人售货柜系统,用户扫码开门、拿走饮料、关门扣款。这个流程涉及好几个环节:订单创建、库存扣减、出货指令下发、支付回调。如果全用HTTP接口同步调用,服务之间强耦合,一个环节慢了整条链路都卡住。
用RocketMQ做消息中间件,服务之间通过消息异步通信,解耦又高效。而SpringBoot + rocketmq-spring-boot-starter 让整合过程极其丝滑,几乎零样板代码。
二、环境准备
确保你已经启动了RocketMQ的NameServer和Broker。如果你还没装,快速用Docker起一套:
# 启动 NameServer
docker run -d --name rmqnamesrv -p 9876:9876 \
apache/rocketmq:5.3.0 sh mqnamesrv
# 启动 Broker
docker run -d --name rmqbroker -p 10911:10911 -p 10909:10909 \
--link rmqnamesrv:namesrv \
-e "NAMESRV_ADDR=namesrv:9876" \
apache/rocketmq:5.3.0 sh mqbroker
本地访问 localhost:9876 是NameServer,localhost:10911 是Broker。
三、pom.xml 引入依赖
创建一个SpringBoot项目,在 pom.xml 中添加依赖:
<!-- SpringBoot 父工程 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.18</version>
</parent>
<dependencies>
<!-- SpringBoot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- RocketMQ SpringBoot Starter -->
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.2.3</version>
</dependency>
<!-- Lombok(简化代码) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
版本兼容性提示:
rocketmq-spring-boot-starter2.2.x 对应 RocketMQ 4.x;如果你用 RocketMQ 5.x,需要用 2.3.0 以上版本。两者API基本一致,本文以4.x为例。
四、application.yml 配置
rocketmq:
# NameServer 地址,多个用分号隔开
name-server: 127.0.0.1:9876
# 生产者配置
producer:
# 生产者组名(必须唯一)
group: vending-producer-group
# 发送超时时间(毫秒)
send-message-timeout: 3000
# 同步发送失败重试次数
retry-times-when-send-failed: 3
# 异步发送失败重试次数
retry-times-when-send-async-failed: 3
server:
port: 8080
消费者配置稍后在注解里写,不需要放yml里。
五、生产者开发:三种发送方式
RocketMQ提供三种发送模式,各有适用场景。
5.1 同步发送(syncSend)
生产者发送消息后阻塞等待Broker返回确认,收到成功响应才继续。
@Service
public class OrderMessageProducer {
@Autowired
private RocketMQTemplate rocketMQTemplate;
/**
* 同步发送订单消息
* 适用场景:重要消息,必须确认送达,如订单创建
*/
public SendResult sendOrderSync(OrderDTO order) {
Message<OrderDTO> message = MessageBuilder
.withPayload(order)
.setHeader("KEYS", order.getOrderId()) // 设置消息Key用于查询
.build();
// topic:order_topic, payload:消息对象
SendResult result = rocketMQTemplate.syncSend("order_topic", message);
System.out.println("发送结果: " + result.getSendStatus());
return result;
}
}
- 可靠性高:发送失败会重试
- 性能低:要等响应,不适合高吞吐
- 场景:售货柜下单、支付通知
5.2 异步发送(asyncSend)
发送后不阻塞,通过回调函数处理发送结果。
/**
* 异步发送出货指令
* 适用场景:对响应时间敏感,但需要知道发送结果
*/
public void sendShipmentAsync(String orderId) {
Message<String> message = MessageBuilder
.withPayload(orderId)
.build();
rocketMQTemplate.asyncSend("shipment_topic", message, new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
log.info("出货指令发送成功: {}", sendResult.getMsgId());
}
@Override
public void onException(Throwable throwable) {
log.error("出货指令发送失败, orderId={}", orderId, throwable);
// TODO: 降级处理,比如写入本地表稍后重发
}
});
}
- 性能高:不阻塞主流程
- 有回调:失败可知
- 场景:出货指令下发、设备控制指令
5.3 单向发送(sendOneWay)
只负责发送,不等响应,不回调。
/**
* 单向发送设备心跳
* 适用场景:日志、心跳等大量不重要的消息
*/
public void sendHeartbeatOneWay(String deviceId, String status) {
Map<String, String> payload = new HashMap<>();
payload.put("deviceId", deviceId);
payload.put("status", status);
payload.put("timestamp", String.valueOf(System.currentTimeMillis()));
rocketMQTemplate.sendOneWay("device_heartbeat_topic", payload);
}
- 性能最高:Fire and forget
- 无可靠性保证:可能丢
- 场景:心跳上报、日志埋点
三种方式对比
| 方式 | 可靠性 | 性能 | 响应 |
|---|---|---|---|
| syncSend | 高 | 低 | 阻塞等结果 |
| asyncSend | 中 | 高 | 回调通知 |
| sendOneWay | 低 | 最高 | 不关心 |
六、消费者开发
消费者开发只需要两步:加注解 + 实现接口。
6.1 基本消费者
@Slf4j
@Component
@RocketMQMessageListener(
topic = "order_topic", // 消费的Topic
consumerGroup = "order_consumer_group", // 消费者组名(唯一)
messageModel = MessageModel.CLUSTERING // 集群模式:同一组下每条消息只被一个消费者消费
)
public class OrderMessageConsumer implements RocketMQListener<OrderDTO> {
@Override
public void onMessage(OrderDTO order) {
log.info("收到订单消息: orderId={}, amount={}",
order.getOrderId(), order.getAmount());
// 业务逻辑:处理订单
processOrder(order);
// 正常返回 = 消费成功
// 抛异常 = 消费失败,会触发重试
}
private void processOrder(OrderDTO order) {
// 实际处理逻辑:扣减库存、记录订单等
log.info("处理订单完成: {}", order.getOrderId());
}
}
小白疑问:什么是集群模式(CLUSTERING)和广播模式(BROADCASTING)?
- 集群模式:同一个consumerGroup下,一条消息只被一个消费者实例消费。适合分布式部署。
- 广播模式:同一个consumerGroup下,每条消息会被所有消费者实例都消费一遍。适合本地缓存刷新。
6.2 注解核心参数详解
@RocketMQMessageListener(
topic = "order_topic",
consumerGroup = "order_consumer_group",
messageModel = MessageModel.CLUSTERING,
selectorExpression = "tag_A || tag_B", // Tag过滤,*表示全部
consumeMode = ConsumeMode.CONCURRENTLY, // 并发消费
maxReconsumeTimes = 5, // 最大重试次数
consumeTimeout = 30000L // 消费超时时间(ms)
)
- consumeMode:
CONCURRENTLY:并发消费,多线程同时处理,速度快但不保证顺序ORDERLY:顺序消费,单线程按队列顺序处理,适合有顺序要求的场景
6.3 消费失败与重试
消费者抛异常 → Broker判定消费失败 → 延迟一段时间后重试。默认重试16次,重试间隔逐步增大(1s→5s→10s→30s→1m→...)。16次后还不成功,进入死信队列(Dead Letter Queue)。
@Override
public void onMessage(OrderDTO order) {
try {
// 业务处理
doBusiness(order);
} catch (Exception e) {
log.error("处理订单失败: {}", order.getOrderId(), e);
// 抛出异常 → 触发重试
throw new RuntimeException("消费失败", e);
}
}
实际生产中,建议对死信队列单独写一个消费者做人工干预处理。
七、消息序列化方案
7.1 默认JSON序列化
rocketmq-spring-boot-starter 默认用 RocketMQMessageConverter 把对象序列化为JSON字符串。你直接传对象就行,不用手动 JSON.toJSONString()。
// 生产者直接发对象
rocketMQTemplate.syncSend("order_topic", orderDTO);
// 消费者直接收对象
public void onMessage(OrderDTO order) { ... }
7.2 自定义MessageConverter
如果默认JSON方案不满足需求(比如你想用Protobuf),可以自定义:
@Configuration
public class RocketMQConfig {
@Bean
public RocketMQMessageConverter rocketMQMessageConverter() {
// 这里可以替换为你自己的序列化器
// 默认是 Jackson JSON
return new RocketMQMessageConverter();
}
}
大多数场景下默认JSON就够了,不用折腾。
八、完整实战:售货柜订单消息
把生产者和消费者串起来,模拟售货柜完整订单链路。
8.1 消息DTO
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class OrderDTO implements Serializable {
private String orderId; // 订单号
private String deviceId; // 售货柜设备ID
private String userId; // 用户ID
private String productId; // 商品ID
private Integer quantity; // 数量
private BigDecimal amount; // 金额
private Long timestamp; // 创建时间
}
8.2 订单Controller(生产者入口)
@RestController
@RequestMapping("/api/order")
public class OrderController {
@Autowired
private OrderMessageProducer producer;
@PostMapping("/create")
public String createOrder(@RequestBody OrderDTO order) {
order.setOrderId(UUID.randomUUID().toString());
order.setTimestamp(System.currentTimeMillis());
// 同步发送订单消息
SendResult result = producer.sendOrderSync(order);
return result.getSendStatus().equals(SendStatus.SEND_OK)
? "订单创建成功: " + order.getOrderId()
: "订单创建失败";
}
}
8.3 订单消费者(处理出货逻辑)
@Slf4j
@Component
@RocketMQMessageListener(
topic = "order_topic",
consumerGroup = "order_consumer_group",
messageModel = MessageModel.CLUSTERING,
maxReconsumeTimes = 5
)
public class OrderMessageConsumer implements RocketMQListener<OrderDTO> {
@Autowired
private InventoryService inventoryService;
@Autowired
private ShipmentService shipmentService;
@Override
public void onMessage(OrderDTO order) {
log.info("处理订单: orderId={}, deviceId={}",
order.getOrderId(), order.getDeviceId());
// 1. 扣减库存
boolean stockOk = inventoryService.deductStock(
order.getProductId(), order.getQuantity());
if (!stockOk) {
throw new RuntimeException("库存不足: " + order.getProductId());
}
// 2. 下发出货指令
shipmentService.sendShipmentCommand(
order.getDeviceId(), order.getProductId(), order.getQuantity());
log.info("订单处理完成: {}", order.getOrderId());
}
}
8.4 出货指令消费者(设备端模拟)
@Slf4j
@Component
@RocketMQMessageListener(
topic = "shipment_topic",
consumerGroup = "shipment_consumer_group",
messageModel = MessageModel.CLUSTERING
)
public class ShipmentConsumer implements RocketMQListener<String> {
@Override
public void onMessage(String orderId) {
log.info("收到出货指令, 准备出货: orderId={}", orderId);
// 模拟设备出货电机转动
// 实际场景中这里会调用设备SDK控制硬件
}
}
九、常见整合问题排查
9.1 版本不兼容
| 现象 | 原因 | 解决 |
|---|---|---|
启动报 org.apache.rocketmq.common.message 相关类找不到 | starter版本与RocketMQ server不匹配 | 4.x server用2.2.x starter;5.x server用2.3.0+ |
| 消费者收不到消息 | Producer和Consumer连的NameServer地址不一致 | 检查yml配置 |
| 消息体反序列化失败 | 生产者和消费者DTO字段不一致 | 确保两端DTO字段名、类型完全一致 |
9.2 消费者不消费
排查清单:
- Topic和Tag是否匹配:producer发的topic和consumer监听的topic要完全一致
- consumerGroup是否被其他实例占用:同一个group+cluster模式只能有一个实例消费
- Broker是否开启autoCreateTopicEnable:默认开启,Topic不存在会自动创建;生产环境建议关闭手动创建
- 防火墙:确认10911端口可达
9.3 序列化异常
org.apache.rocketmq.spring.support.RocketMQMessageConverter ...
Cannot deserialize
常见原因:DTO没有无参构造方法。加上 @NoArgsConstructor 即可。另外DTO必须实现 Serializable。
十、总结
SpringBoot整合RocketMQ的核心套路就是三步:
1. 引依赖 → 2.1.x/2.2.x/2.3.0+选对版本
2. 配yml → name-server + producer.group
3. 写代码 → 注入Template发消息 / 加注解收消息
三种发送方式按场景选:
- 同步 → 重要消息(订单、支付)
- 异步 → 高吞吐且需回调(出货指令)
- 单向 → 不重要的海量消息(心跳、日志)
消费者记住 @RocketMQMessageListener 注解那几个参数,配合 RocketMQListener<T> 接口,基本能覆盖80%的业务场景。剩下20%的事务消息和顺序消息,后面的文章会专门讲。