SpringBoot整合RocketMQ:生产者、消费者快速搭建

0 阅读7分钟

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-starter 2.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 消费者不消费

排查清单:

  1. Topic和Tag是否匹配:producer发的topic和consumer监听的topic要完全一致
  2. consumerGroup是否被其他实例占用:同一个group+cluster模式只能有一个实例消费
  3. Broker是否开启autoCreateTopicEnable:默认开启,Topic不存在会自动创建;生产环境建议关闭手动创建
  4. 防火墙:确认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%的事务消息和顺序消息,后面的文章会专门讲。