RabbitMQ消费者确认原理

RabbitMQ消费者确认详解

什么是消费者确认?

在 Spring 中操作 RabbitMQ 时,消费者确认机制的实现主要依赖 Spring AMQP(Spring 对 AMQP 协议的封装,底层基于 RabbitMQ Java 客户端)。Spring 提供了灵活的配置方式,支持自动确认和手动确认和默认确认3种模式。

使用方法

1.引入依赖

<!-- Spring AMQP 核心依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

2.配置确认模式

spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
    listener:
      simple:
        acknowledge-mode: manual  # 手动确认模式 ,auto为自动,none为默认
        prefetch: 1  # 每次从队列拉取1条消息,处理完再拉取下一条(避免消息堆积,手动模式需要)
        default-requeue-rejected: false  # 拒绝消息时是否重新入队(手动模式下可忽略,由代码控制)

3.三种默认的区别

3.1手动模式(适合核心业务)

在手动确认模式下,消费者需要通过 Channel 对象显式调用确认方法(basicAck/basicNack/basicReject)。Spring 会将消息的 Channel 和 DeliveryTag 封装到 Message 对象中,可通过参数注入获取。

import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import com.rabbitmq.client.Channel;

@Component
public class RabbitConsumer {

    // 监听队列:test.queue
    @RabbitListener(queues = "test.queue")
    public void handleMessage(String messageContent, Channel channel, Message message) throws Exception {
        long deliveryTag = message.getMessageProperties().getDeliveryTag(); // 获取消息唯一标识
        try {
            // 1. 处理业务逻辑(例如:解析消息、操作数据库等)
            System.out.println("处理消息:" + messageContent);
            
            // 2. 处理成功,手动确认
            // 参数1:deliveryTag(消息标识)
            // 参数2:multiple(是否批量确认,false表示仅确认当前消息)
            channel.basicAck(deliveryTag, false);
            System.out.println("消息确认成功,deliveryTag: " + deliveryTag);
            
        } catch (Exception e) {
            // 3. 处理失败,拒绝消息
            // basicNack支持批量拒绝,basicReject仅支持单条
            // 参数3:requeue(是否重新入队,false则消息进入死信队列或被丢弃)
            channel.basicNack(deliveryTag, false, false); 
            // 或使用 basicReject:channel.basicReject(deliveryTag, false);
            System.out.println("消息处理失败,已拒绝,deliveryTag: " + deliveryTag);
        }
    }
}
3.2默认模式(不推荐)

默认模式下,消息只要被消费者接收,无论是否抛出异常,都会将队列的消息删除

@RabbitListener(queues = "test.queue")
public void handleMessageAutoAck(String messageContent) {
    System.out.println("自动确认模式,处理消息:" + messageContent);
    // 无需手动确认,消息被接收后立即从队列删除
}
3.3自动确认(适合非核心业务)

auto 模式下,Spring 会根据业务逻辑是否抛出异常自动确认或拒绝:

@RabbitListener(queues = "test.queue")
public void handleMessageAuto(String messageContent) {
    System.out.println("处理消息:" + messageContent);
    // 若业务逻辑无异常,Spring 自动调用 basicAck
    // 若抛出异常,Spring 自动调用 basicNack(并根据 default-requeue-rejected 决定是否重新入队)
    if (messageContent.contains("error")) {
        throw new RuntimeException("处理失败"); // 触发自动拒绝
    }
}

关键注意事项

  1. 手动确认的时机:必须在业务逻辑处理完成后再调用 basicAck,避免提前确认导致消息丢失(例如:处理过程中崩溃)。
  2. prefetch 配置:手动模式下建议设置 prefetch(每次拉取的消息数),防止消费者端堆积大量未确认消息(若消费者崩溃,这些消息会被重新投递,可能导致重复处理)。
  3. 死信队列配合:若消息处理失败且不需要重新入队(requeue=false),建议配置死信队列(DLQ),将失败消息转发到 DLQ 以便后续分析,避免消息丢失。
  4. 幂等性处理:由于消息可能被重新投递(如消费者崩溃后重发),需确保消费者逻辑支持幂等性(重复处理不会导致业务异常)。

4.如何实现非核心业务和核心业务采用不同确认模式

核心思路

Spring AMQP 允许通过 @RabbitListener 的 containerFactory 属性 指定不同的监听器容器工厂,每个工厂可配置独立的确认模式(手动 / 自动)。因此,只需:

  1. 定义两个监听器容器工厂(一个手动确认,一个自动确认)。
  2. 核心业务的消费者指定手动确认的工厂,非核心业务指定自动确认的工厂。
步骤 1:配置两个监听器容器工厂

在配置类中定义两个 SimpleRabbitListenerContainerFactory 实例,分别对应手动确认和自动确认模式。

import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitMQConfig {

    // 1. 消息转换器(JSON 序列化,全局共用)
    @Bean
    public MessageConverter messageConverter() {
        return new Jackson2JsonMessageConverter();
    }

    // 2. 手动确认模式的容器工厂(核心业务用)
    @Bean(name = "manualAckContainerFactory")
    public SimpleRabbitListenerContainerFactory manualAckContainerFactory(ConnectionFactory connectionFactory) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);
        factory.setMessageConverter(messageConverter()); // 使用 JSON 转换器
        factory.setAcknowledgeMode(AcknowledgeMode.MANUAL); // 手动确认
        factory.setPrefetchCount(1); // 每次拉取1条消息,处理完再拉取(确保可靠)
        return factory;
    }

   // AUTO 模式的容器工厂(非核心业务用)
    @Bean(name = "autoAckContainerFactory")
    public SimpleRabbitListenerContainerFactory autoAckContainerFactory(ConnectionFactory connectionFactory) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);
        factory.setMessageConverter(new Jackson2JsonMessageConverter());
        factory.setAcknowledgeMode(AcknowledgeMode.AUTO); // 关键:AUTO 模式
        factory.setDefaultRequeueRejected(true); // 处理异常时自动重入队(默认值也是 true)
        factory.setPrefetchCount(10); // 适当提高吞吐量
        return factory;
    }
}

核心业务消费者

import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import com.rabbitmq.client.Channel;

@Component
public class CoreBusinessConsumer {

    // 核心业务:订单处理(使用手动确认工厂)
    @RabbitListener(queues = "core.order.queue", containerFactory = "manualAckContainerFactory")
    public void handleOrder(String orderId, Channel channel, Message message) throws Exception {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try {
            // 处理核心业务(如订单支付、库存扣减)
            System.out.println("处理订单:" + orderId);
            // 业务处理成功,手动确认
            channel.basicAck(deliveryTag, false);
        } catch (Exception e) {
            // 处理失败,拒绝消息(进入死信队列)
            channel.basicNack(deliveryTag, false, false);
        }
    }
}

非核心业务

@Component
public class NonCoreConsumer {

    // 非核心业务:如用户行为日志收集
    @RabbitListener(queues = "noncore.behavior.queue", containerFactory = "autoAckContainerFactory")
    public void handleBehaviorLog(String log) {
        System.out.println("处理行为日志:" + log);
        // 若处理中抛出异常(如数据库临时不可用),Spring 会自动拒绝消息并重新入队
        if (log.contains("error")) {
            throw new RuntimeException("临时处理失败"); // 触发自动重入队
        }
    }
}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值