springboot整合rocketmq,如何在消费端采用多线程消费

918 阅读3分钟

1. 使用@RocketMQMessageListener注解

(默认模式)

如果你使用的是org.apache.rocketmq.spring.annotation.RocketMQMessageListener注解来配置消息监听器,你可以在监听器内部实现多线程消费。这通常通过在监听器内部启动一个线程池来处理消息。

  • 手动提交ack
import org.apache.rocketmq.spring.annotation.ConsumeMode;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Service;
 
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
@Service
@RocketMQMessageListener(topic = "your-topic", consumerGroup = "your-group", consumeMode = ConsumeMode.CONCURRENTLY)
public class YourMessageListener implements RocketMQListener<String> {
    private final ExecutorService executor = Executors.newFixedThreadPool(10); // 创建一个固定大小的线程池
 
    @Override
    public void onMessage(String message) {
        executor.submit(() -> {
            // 处理消息的逻辑
            System.out.println("Received: " + message);
            // 确保资源释放,例如关闭数据库连接等
        });
    }
}

在异步并发消费模式下,当消息处理完成后,会自动提交 ACK。

提交时机说明

  • 正常处理:如果 onMessage 方法正常执行完毕,没有抛出异常,RocketMQ 客户端会自动认为消息消费成功,并向 Broker 提交 ACK,表示该消息已被成功消费。
  • 异常处理:如果 onMessage 方法抛出异常,RocketMQ 会根据重试策略对消息进行重试。默认情况下,消息会重试 16 次,每次重试的间隔时间逐渐增加。如果重试次数达到上限仍然失败,消息会进入死信队列。

手动 ACK 模式

MessageListenerConcurrently 是 Apache RocketMQ 提供的一个消息监听器接口,主要用于实现并发消费消息的功能。


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.common.message.MessageExt;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
@RocketMQMessageListener(topic = "your-topic", consumerGroup = "your-group", consumeMode = ConsumeMode.CONCURRENTLY)
public class ManualAckConsumer implements MessageListenerConcurrently  {
    @Override
    public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
        for (MessageExt msg : msgs) {
            try {
                // 处理消息的业务逻辑
                System.out.println("Received message: " + new String(msg.getBody()));
            } catch (Exception e) {
                // 处理异常
                e.printStackTrace();
                // 消费失败,返回 RECONSUME_LATER 进行重试
                return ConsumeConcurrentlyStatus.RECONSUME_LATER;
            }
        }
        // 消费成功,手动提交 ACK
        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
    }
}

提交时机说明

  • 消费成功:当一个消息队列中的消息依次处理完成,且 consumeMessage 方法返回 ConsumeOrderlyStatus.SUCCESS 时,会向 Broker 提交 ACK,表示该消息队列中的消息已全部成功消费。

  • 消费失败:如果在消息处理过程中出现异常,返回 ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT,表示当前消息队列需要暂停消费一段时间,RocketMQ 会对消息进行重试。

综上所述,ACK 的提交时机取决于消费模式和消息处理结果,你可以根据具体的业务需求选择合适的消费模式和 ACK 提交方式。

在某些场景下,你可能需要手动控制 ACK 的提交,这时可以使用 RocketMQReplyListener 接口,并结合 MessageListenerConcurrently 实现手动 ACK。

2. 使用DefaultMQPushConsumer手动管理消费者

如果你需要更细粒度的控制,比如使用DefaultMQPushConsumer手动创建和配置消费者,你也可以在其中实现多线程逻辑。

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.common.message.MessageExt;
 
import java.util.List;
import java.util.concurrent.*;
 
public class YourConsumer {
    private final ExecutorService executor = Executors.newFixedThreadPool(10); // 创建一个固定大小的线程池
 
    public static void main(String[] args) throws Exception {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("your-group");
        consumer.setNamesrvAddr("localhost:9876"); // 设置NameServer地址
        consumer.subscribe("your-topic", "*"); // 订阅主题和标签
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
                msgs.forEach(msg -> executor.submit(() -> {
                    // 处理消息的逻辑
                    System.out.println("Received: " + new String(msg.getBody()));
                }));
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; // 消费成功
            }
        });
        consumer.start();
        System.out.printf("Consumer Started.%n");
    }
}