如何在 Spring Boot 中实现 RocketMQ 的批量消息消费

前言

批量消费是 RocketMQ 提供的一种消费模式,能够有效提升消费者的处理能力,减少网络 IO 操作,从而提高性能。

在使用 @RocketMQMessageListener 注解时,Spring Boot 默认的消费模式并不支持批量消费。为了在高吞吐量场景下优化消费性能,我们可以通过手动配置 RocketMQ 消费者,来实现批量消息消费。

本文将详细介绍如何从零开始,在 Spring Boot 项目中集成 RocketMQ 的批量消费功能,并提供一些性能优化建议。

接入流程

  1. 引入依赖
  2. 配置文件 bootstrap.yaml
  3. 配置类 MqConfigProperties
  4. 消费者代码实现
  5. 生产者示例代码
  6. 其他优化建议

1. 引入依赖

java 复制代码
<!--RocketMQ的Spring Boot依赖,适用于Spring Boot 3版本-->
<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-spring-boot-starter</artifactId>
    <version>2.3.1</version>
    <exclusions>
       <exclusion>
          <groupId>org.apache.rocketmq</groupId>
          <artifactId>rocketmq-client</artifactId>
       </exclusion>
    </exclusions>
</dependency>

<!-- 兼容MQ集群5.3.0版本的依赖 -->
<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-client</artifactId>
    <version>5.3.0</version>
</dependency>

2. 配置文件 bootstrap.yaml

yaml 复制代码
rocketmq:
  name-server: 192.168.1.1:9876;192.168.1.2:9876;192.168.1.3:9876 # 替换为实际的NameServer地址
  consumer:
    group: consume-group-test
    access-key: access # 如果使用了ACL,则需要配置
    secret-key: secret
    consume-message-batch-max-size: 50  # 每次批量消费的最大消息数
    pull-batch-size: 100  # 每次从Broker拉取的消息数
  topics:
    project: "group-topic-1"
  groups:
    project: "consume-group-1"  # 不同业务推荐使用不同的消费组
    

3. 配置类 MqConfigProperties

java 复制代码
import org.apache.rocketmq.spring.autoconfigure.RocketMQProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import lombok.Data;

import java.io.Serializable;

/**
 * RocketMQ 配置类
 */
@Data
@Component
@ConfigurationProperties(prefix = "rocketmq")
public class MqConfigProperties implements Serializable {

    private static final long serialVersionUID = 1L;

    @Autowired
    private RocketMQProperties rocketMQProperties;

    private TopicProperties topics;
    private GroupProperties groups;

    /**
     * 主题配置类
     */
    @Data
    public static class TopicProperties implements Serializable {
       private static final long serialVersionUID = 1L;
       private String project;
    }

    /**
     * 消费组配置类
     */
    @Data
    public static class GroupProperties implements Serializable {
       private static final long serialVersionUID = 1L;
       private String project;
    }
}

4. 消费者代码实现

java 复制代码
import com.alibaba.fastjson2.JSONObject;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.client.consumer.rebalance.AllocateMessageQueueAveragely;
import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.remoting.RPCHook;
import org.apache.rocketmq.spring.support.RocketMQUtil;
import org.springframework.context.ApplicationContext;
import org.springframework.context.SmartLifecycle;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;

import javax.annotation.Resource;
import java.util.List;

/**
 * 批量消费实现
 */
@Component
@Slf4j
public class UserConsumer implements SmartLifecycle {

    @Resource
    private MqConfigProperties mqConfigProperties;

    @Resource
    private ApplicationContext applicationContext;

    private volatile boolean running;
    private DefaultMQPushConsumer consumer;

    @Override
    public void start() {
        if (isRunning()) {
            throw new IllegalStateException("Consumer is already running");
        }
        initConsumer();
        setRunning(true);
        log.info("UserConsumer started successfully.");
    }

    @Override
    public void stop() {
        if (isRunning() && consumer != null) {
            consumer.shutdown();
            setRunning(false);
            log.info("UserConsumer stopped.");
        }
    }

    @Override
    public boolean isRunning() {
        return running;
    }

    private void setRunning(boolean running) {
        this.running = running;
    }

    private void initConsumer() {
        String topic = mqConfigProperties.getTopics().getProject();
        String group = mqConfigProperties.getGroups().getProject();
        String nameServer = mqConfigProperties.getRocketMQProperties().getNameServer();
        String accessKey = mqConfigProperties.getRocketMQProperties().getConsumer().getAccessKey();
        String secretKey = mqConfigProperties.getRocketMQProperties().getConsumer().getSecretKey();

        RPCHook rpcHook = RocketMQUtil.getRPCHookByAkSk(applicationContext.getEnvironment(), accessKey, secretKey);
        consumer = rpcHook != null
                ? new DefaultMQPushConsumer(group, rpcHook, new AllocateMessageQueueAveragely())
                : new DefaultMQPushConsumer(group);

        consumer.setNamesrvAddr(nameServer);
        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);
      
        // 设置每次批量消费的消息数量
        consumer.setConsumeMessageBatchMaxSize(100);
        consumer.subscribe(topic, "*");
        consumer.setMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
                log.info("Received {} messages", msgs.size());
                for (MessageExt message : msgs) {
                    String body = new String(message.getBody());
                    log.info("Processing message: {}", body);
                    User user = JSONObject.parseObject(body, User.class);
                    processUser(user);
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });

        consumer.start();
        log.info("UserConsumer initialized with topic [{}] and group [{}].", topic, group);
    }

    private void processUser(User user) {
        log.info("Processing user with ID: {}", user.getId());
        // 处理用户相关的业务逻辑
    }
}

5. 生产者示例代码

java 复制代码
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.common.message.Message;

import java.util.ArrayList;
import java.util.List;

public class UserProducer {

    private DefaultMQProducer producer;

    public void sendBatchMessages(List<User> users, String topic) {
        List<Message> messages = new ArrayList<>();
        for (User user : users) {
            messages.add(new Message(topic, JSONObject.toJSONString(user).getBytes()));
        }
        try {
            producer.send(messages);
        } catch (Exception e) {
            log.error("Error sending batch messages", e);
        }
    }
}

6. 其他优化建议

  1. 性能优化:可以灵活调整消费线程池的大小,默认设置为 consumeThreadMin=20 和 consumeThreadMax=20,但在高并发场景下可以增加线程池的大小来提升性能。

  2. 错误处理:当消费发生异常时,应该谨慎使用 RECONSUME_LATER,避免陷入无限重试循环,可以根据具体的业务场景设置最大重试次数。

  3. 租户隔离:不同业务模块使用不同的 group,避免误消费数据,尤其在生产环境下这点非常重要。

相关推荐
大叔_爱编程39 分钟前
wx030基于springboot+vue+uniapp的养老院系统小程序
vue.js·spring boot·小程序·uni-app·毕业设计·源码·课程设计
计算机学姐3 小时前
基于微信小程序的驾校预约小程序
java·vue.js·spring boot·后端·spring·微信小程序·小程序
小白的一叶扁舟4 小时前
深入剖析 JVM 内存模型
java·jvm·spring boot·架构
sjsjsbbsbsn4 小时前
基于注解实现去重表消息防止重复消费
java·spring boot·分布式·spring cloud·java-rocketmq·java-rabbitmq
苹果醋34 小时前
golang 编程规范 - Effective Go 中文
java·运维·spring boot·mysql·nginx
chengpei1474 小时前
实现一个自己的spring-boot-starter,基于SQL生成HTTP接口
java·数据库·spring boot·sql·http
Leaf吧7 小时前
springboot 配置多数据源以及动态切换数据源
java·数据库·spring boot·后端
荆州克莱8 小时前
Golang的网络编程安全
spring boot·spring·spring cloud·css3·技术
m0_748230949 小时前
SpringBoot实战(三十二)集成 ofdrw,实现 PDF 和 OFD 的转换、SM2 签署OFD
spring boot·后端·pdf
不一样的信息安全11 小时前
Spring Boot框架下的上海特产销售商城网站开发之旅
网络·spring boot