RabbitMQ 实战(四)消费者 ack 以及 生产者 confirms

本文详细探讨了RabbitMQ中消费者ACK(自动与手动)和生产者Confirms模式。在消费者手动ACK模式下,消息需被确认才能避免丢失;而在生产者Confirms模式下,通过ReturnCallback和ConfirmCallback确保消息成功送达。需要注意,即使ConfirmCallback返回true,仍可能存在消息未到达队列的情况。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这篇文章主要讲 RabbitMQ 中 消费者 ack 以及 生产者 confirms。

如上图,生产者把消息发送到 RabbitMQ,然后 RabbitMQ 再把消息投递到消费者。

生产者和 RabbitMQ,以及 RabbitMQ 和消费者都是通过 TCP 连接,但是他们之间是通过信道(Channel)传递数据的。多个线程共享一个连接,但是每个线程拥有独自的信道。

消费者 ack

  • 问题:怎么保证 RabbitMQ 投递的消息被成功投递到了消费者?

    RabbitMQ 投递的消息,刚投递一半,产生了网络抖动,就有可能到不了消费者。

  • 解决办法:

    RabbitMQ 对消费者说:“如果你成功接收到了消息,给我说确认收到了,不然我就当你没有收到,我还会重新投递”

在 RabbitMQ 中,有两种 acknowledgement 模式。

自动 acknowledgement 模式

这也称作发后即忘模式

在这种模式下,RabbitMQ 投递了消息,在投递成功之前,如果消费者的 TCP 连接 或者 channel 关闭了,这条消息就会丢失。

会有丢失消息问题。

手动 acknowledgement 模式

在这种模式下,RabbitMQ 投递了消息,在投递成功之前,如果消费者的 TCP 连接 或者 channel 关闭了,导致这条消息没有被 acked,RabbitMQ 会自动把当前消息重新入队,再次投递。

会有重复投递消息的问题,所以消费者得准备好处理重复消息的问题,就是所谓的:幂等性。

为了启用 手动 ack 模式,消费者需要实现 ChannelAwareMessageListener 接口。

@Component
public class Consumer implements ChannelAwareMessageListener {
   
   

    @Autowired
    private MessageConverter messageConverter;

    @Override
    public void onMessage(Message message, Channel channel) throws Exception {
        MessageProperties messageProperties = message.getMessageProperties();

        // 代表投递的标识符,唯一标识了当前信道上的投递,通过 deliveryTag ,消费者就可以告诉 RabbitMQ 确认收到了当前消息,见下面的方法
        long deliveryTag = messageProperties.getDeliveryTag();

        // 如果是重复投递的消息,redelivered 为 true
        Boolean redelivered = messageProperties.getRedelivered();

        // 获取生产者发送的原始消息
        Object originalMessage = messageConverter.fromMessage(message);

        Console.log("consume message = {} , deliveryTag = {} , redelivered = {}"
                , originalMessage, deliveryTag, redelivered);

        // 代表消费者确认收到当前消息,第二个参数表示一次是否 ack 多条消息
        channel.basicAck(deliveryTag, false);

        // 代表消费者拒绝一条或者多条消息,第二个参数表示一次是否拒绝多条消息,第三个参数表示是否把当前消息重新入队
//        channel.basicNack(deliveryTag, false, false);

        
<think>根据用户需求,我们将提供完整的Spring整合RabbitMQ的XML配置,并将生产者消费者的配置分开。同时,我们将参考用户提供的引用内容,特别是关于连接配置、并发设置和消息确认模式的部分。 ### 完整XML配置方案 我们将配置拆分为三个文件: 1. `rabbitmq-common.xml` - 公共配置(连接工厂、RabbitAdmin等) 2. `rabbitmq-producer.xml` - 生产者配置 3. `rabbitmq-consumer.xml` - 消费者配置 --- ### 1. 公共配置 (`rabbitmq-common.xml`) ```xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rabbit="http://www.springframework.org/schema/rabbit" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit.xsd"> <!-- 加载RabbitMQ属性文件 --> <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="location" value="classpath:rabbitmq.properties"/> </bean> <!-- RabbitMQ连接工厂 --> <rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}" port="${rabbitmq.port}" username="${rabbitmq.username}" password="${rabbitmq.password}" virtual-host="${rabbitmq.virtual-host}" channel-cache-size="25"/> <!-- RabbitAdmin: 用于声明队列、交换器等 --> <rabbit:admin connection-factory="connectionFactory" auto-startup="true"/> </beans> ``` 对应的 `rabbitmq.properties` 文件内容: ```properties # RabbitMQ连接配置 rabbitmq.host=127.0.0.1 rabbitmq.port=5672 rabbitmq.username=guest rabbitmq.password=guest rabbitmq.virtual-host=/ ``` --- ### 2. 生产者配置 (`rabbitmq-producer.xml`) ```xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rabbit="http://www.springframework.org/schema/rabbit" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit.xsd"> <!-- 导入公共配置 --> <import resource="rabbitmq-common.xml"/> <!-- 声明队列 --> <rabbit:queue id="orderQueue" name="order.queue" durable="true" auto-delete="false"/> <!-- 声明直连交换器 --> <rabbit:direct-exchange name="order.exchange" durable="true"> <rabbit:bindings> <rabbit:binding queue="orderQueue" key="order.key"/> </rabbit:bindings> </rabbit:direct-exchange> <!-- RabbitTemplate配置 --> <rabbit:template id="rabbitTemplate" connection-factory="connectionFactory" exchange="order.exchange" routing-key="order.key" message-converter="jsonMessageConverter"/> <!-- JSON消息转换器 --> <bean id="jsonMessageConverter" class="org.springframework.amqp.support.converter.Jackson2JsonMessageConverter"/> <!-- 生产者Bean --> <bean id="orderProducer" class="com.example.producer.OrderProducer"> <property name="rabbitTemplate" ref="rabbitTemplate"/> </bean> </beans> ``` 生产者实现类: ```java public class OrderProducer { private RabbitTemplate rabbitTemplate; public void setRabbitTemplate(RabbitTemplate rabbitTemplate) { this.rabbitTemplate = rabbitTemplate; } public void sendOrder(Order order) { rabbitTemplate.convertAndSend(order); System.out.println("Sent order: " + order); } } ``` --- ### 3. 消费者配置 (`rabbitmq-consumer.xml`) ```xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rabbit="http://www.springframework.org/schema/rabbit" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit.xsd"> <!-- 导入公共配置 --> <import resource="rabbitmq-common.xml"/> <!-- 声明队列(与生产者一致) --> <rabbit:queue id="orderQueue" name="order.queue" durable="true" auto-delete="false"/> <!-- JSON消息转换器 --> <bean id="jsonMessageConverter" class="org.springframework.amqp.support.converter.Jackson2JsonMessageConverter"/> <!-- 消息监听器 --> <bean id="orderListener" class="com.example.consumer.OrderListener"/> <!-- 监听容器配置 --> <rabbit:listener-container connection-factory="connectionFactory" acknowledge="manual" <!-- 手动ACK --> concurrency="5" <!-- 最小消费者数 --> max-concurrency="10" <!-- 最大消费者数 --> prefetch="1" <!-- 每次预取消息数 --> message-converter="jsonMessageConverter"> <rabbit:listener ref="orderListener" queues="orderQueue" method="handleOrder"/> </rabbit:listener-container> </beans> ``` 消费者实现类(手动ACK模式): ```java public class OrderListener { // 方法参数可以是自定义对象(由消息转换器转换) public void handleOrder(Order order, Channel channel, Message message) throws IOException { try { // 业务处理逻辑 System.out.println("Processing order: " + order); // 手动确认消息(第二个参数表示是否批量确认) channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); } catch (Exception e) { // 处理失败,拒绝消息(第三个参数表示是否重新入队) channel.basicReject(message.getMessageProperties().getDeliveryTag(), true); } } } ``` --- ### 主应用配置 (`applicationContext.xml`) ```xml <beans> <!-- 公共配置 --> <import resource="rabbitmq-common.xml"/> <!-- 按需导入生产者消费者配置 --> <!-- 生产者应用导入 --> <import resource="rabbitmq-producer.xml"/> <!-- 消费者应用导入 --> <!-- <import resource="rabbitmq-consumer.xml"/> --> </beans> ``` --- ### 关键特性说明 1. **连接配置外部化**: - 使用`.properties`文件管理连接参数[^2] - 支持不同环境配置切换 2. **生产者配置**: - 使用`Jackson2JsonMessageConverter`实现JSON序列化[^3] - 声明式绑定路由关系 3. **消费者配置**: - 手动ACK模式(`acknowledge="manual"`)确保可靠消费[^3] - 动态消费者线程池(`concurrency`和`max-concurrency`)[^1] - 每次预取1条消息(`prefetch="1"`)避免消息堆积 4. **异常处理**: - 消费失败时通过`basicReject`重新入队 - 支持死信队列配置(未展示,可扩展) --- ### 依赖配置 (`pom.xml`) ```xml <dependencies> <!-- Spring RabbitMQ --> <dependency> <groupId>org.springframework.amqp</groupId> <artifactId>spring-rabbit</artifactId> <version>2.4.5</version> </dependency> <!-- Jackson for JSON --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.13.3</version> </dependency> <!-- Spring Context --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.18</version> </dependency> </dependencies> ``` --- ### 使用示例 **生产者应用**: ```java public class ProducerApp { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); OrderProducer producer = context.getBean("orderProducer", OrderProducer.class); Order order = new Order("12345", 99.99); producer.sendOrder(order); } } ``` **消费者应用**: ```java public class ConsumerApp { public static void main(String[] args) { new ClassPathXmlApplicationContext("rabbitmq-consumer.xml"); System.out.println("Consumer started..."); } } ``` --- ### 注意事项 1. **队列声明幂等性**: - 生产者消费者都声明同名队列是安全的 - RabbitMQ会验证队列参数一致性 2. **手动ACK必要性**: - 业务关键消息必须使用手动ACK - 自动ACK可能导致消息丢失 3. **消费者并发控制**: - `prefetch`值不宜过大,避免单节点过载 - 根据业务处理时间调整并发数[^1] 4. **消息转换器一致性**: - 生产者消费者必须使用相同的消息格式 - JSON转换器需确保类路径上有Jackson库 --- ### §§相关问题§§ 1. 如何在Spring XML配置中实现RabbitMQ消息的延迟投递(延迟队列)? 2. 如何配置RabbitMQ消费者在消费失败时的重试策略? 3. 在Spring整合RabbitMQ时,如何实现生产者的消息确认(Publisher Confirm)机制? 4. 如何为不同的消息类型配置多个RabbitMQ监听容器?</think>下面为您提供 Spring 整合 RabbitMQ 的完整 XML 配置方案,将生产者消费者配置分开,并包含详细的实现代码: ### 配置方案结构 ``` src ├── main │ ├── resources │ │ ├── rabbitmq-producer.xml # 生产者配置 │ │ ├── rabbitmq-consumer.xml # 消费者配置 │ │ ├── rabbitmq-common.xml # 公共配置 │ │ └── rabbitmq.properties # MQ连接配置 │ └── java │ ├── producer │ │ └── MessageProducer.java # 生产者实现 │ └── consumer │ └── MessageListener.java # 消费者实现 ``` --- ### 1. 公共配置 (`rabbitmq-common.xml`) ```xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rabbit="http://www.springframework.org/schema/rabbit" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 加载属性文件 --> <context:property-placeholder location="classpath:rabbitmq.properties"/> <!-- RabbitMQ 连接工厂 --> <rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}" port="${rabbitmq.port}" username="${rabbitmq.username}" password="${rabbitmq.password}" virtual-host="${rabbitmq.virtual-host}" channel-cache-size="25"/> <!-- RabbitAdmin 用于声明队列和交换器 --> <rabbit:admin connection-factory="connectionFactory"/> <!-- JSON 消息转换器 --> <bean id="jsonMessageConverter" class="org.springframework.amqp.support.converter.Jackson2JsonMessageConverter"/> </beans> ``` ### 2. RabbitMQ 连接配置 (`rabbitmq.properties`) ```properties # RabbitMQ 连接配置 rabbitmq.host=127.0.0.1 rabbitmq.port=5672 rabbitmq.username=guest rabbitmq.password=guest rabbitmq.virtual-host=/ ``` --- ### 3. 生产者配置 (`rabbitmq-producer.xml`) ```xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rabbit="http://www.springframework.org/schema/rabbit" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit.xsd"> <!-- 导入公共配置 --> <import resource="rabbitmq-common.xml"/> <!-- 声明队列 --> <rabbit:queue id="orderQueue" name="order.queue" durable="true" auto-delete="false"/> <!-- 声明直连交换器 --> <rabbit:direct-exchange name="order.exchange" durable="true"> <rabbit:bindings> <rabbit:binding queue="orderQueue" key="order.key"/> </rabbit:bindings> </rabbit:direct-exchange> <!-- RabbitTemplate 配置 --> <rabbit:template id="rabbitTemplate" connection-factory="connectionFactory" exchange="order.exchange" routing-key="order.key" message-converter="jsonMessageConverter"/> <!-- 生产者Bean --> <bean id="messageProducer" class="producer.MessageProducer"> <property name="rabbitTemplate" ref="rabbitTemplate"/> </bean> </beans> ``` ### 生产者实现 (`MessageProducer.java`) ```java public class MessageProducer { private RabbitTemplate rabbitTemplate; public void setRabbitTemplate(RabbitTemplate rabbitTemplate) { this.rabbitTemplate = rabbitTemplate; } public void sendOrder(Order order) { rabbitTemplate.convertAndSend(order); System.out.println("Sent order: " + order); } // 消息确认回调 private final RabbitTemplate.ConfirmCallback confirmCallback = (correlationData, ack, cause) -> { if (ack) { System.out.println("Message confirmed: " + correlationData); } else { System.err.println("Message failed: " + cause); } }; } ``` --- ### 4. 消费者配置 (`rabbitmq-consumer.xml`) ```xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rabbit="http://www.springframework.org/schema/rabbit" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit.xsd"> <!-- 导入公共配置 --> <import resource="rabbitmq-common.xml"/> <!-- 声明队列(与生产者一致) --> <rabbit:queue id="orderQueue" name="order.queue" durable="true" auto-delete="false"/> <!-- 消息监听容器 --> <rabbit:listener-container connection-factory="connectionFactory" acknowledge="manual" <!-- 手动ACK --> concurrency="5" <!-- 最小消费者数 --> max-concurrency="10" <!-- 最大消费者数 --> prefetch="1" <!-- 每次预取消息数 --> message-converter="jsonMessageConverter"> <rabbit:listener ref="messageListener" queues="orderQueue" method="handleOrder"/> </rabbit:listener-container> <!-- 消息监听器 --> <bean id="messageListener" class="consumer.MessageListener"/> </beans> ``` ### 消费者实现 (`MessageListener.java`) ```java public class MessageListener { // 处理订单消息(参数自动转换) public void handleOrder(Order order, Channel channel, Message message) throws IOException { try { // 业务处理逻辑 System.out.println("Processing order: " + order); // 手动确认消息(第二个参数表示是否批量确认) channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); } catch (Exception e) { // 处理失败,拒绝消息(第三个参数表示是否重新入队) channel.basicReject(message.getMessageProperties().getDeliveryTag(), true); } } } ``` --- ### 5. 主应用配置 ```xml <!-- 生产者应用 --> <import resource="rabbitmq-producer.xml"/> <!-- 消费者应用 --> <import resource="rabbitmq-consumer.xml"/> ``` --- ### 关键配置说明 1. **连接配置**: - 使用外部属性文件管理连接参数[^2][^3] - 支持虚拟主机配置 2. **生产者特性**: - 使用 `Jackson2JsonMessageConverter` 实现JSON序列化[^3] - 支持消息确认回调(需配置 `publisher-confirms="true"`) 3. **消费者特性**: - 手动ACK模式确保可靠消费[^3] - 动态消费者线程池(`concurrency` 和 `max-concurrency`)[^1] - 每次预取1条消息(`prefetch="1"`)避免消息堆积 4. **队列声明**: - `durable="true"` 确保队列持久化 - `auto-delete="false"` 连接断开时保留队列 --- ### 使用示例 ```java // 启动生产者 public class ProducerApp { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("rabbitmq-producer.xml"); MessageProducer producer = context.getBean("messageProducer", MessageProducer.class); Order order = new Order("12345", 99.99); producer.sendOrder(order); } } // 启动消费者 public class ConsumerApp { public static void main(String[] args) { new ClassPathXmlApplicationContext("rabbitmq-consumer.xml"); System.out.println("Consumer started..."); } } ``` --- ### 依赖配置 (`pom.xml`) ```xml <dependencies> <!-- Spring RabbitMQ --> <dependency> <groupId>org.springframework.amqp</groupId> <artifactId>spring-rabbit</artifactId> <version>2.4.5</version> </dependency> <!-- Jackson for JSON --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.13.3</version> </dependency> <!-- Spring Context --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.18</version> </dependency> </dependencies> ``` --- ### 注意事项 1. **队列声明一致性**:生产者消费者声明的队列参数必须一致 2. **消息转换器**:生产者消费者需使用相同的消息格式 3. **手动ACK**:业务关键消息必须手动确认 4. **连接管理**:生产环境建议配置连接池参数 5. **错误处理**:消费者需实现健壮的错误处理逻辑 --- ###
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值