


个人主页:手握风云
目录
[1.1. autoAck 自动确认](#1.1. autoAck 自动确认)
[1.2. 手动确认](#1.2. 手动确认)
[1.3. SpringBoot 三种模式](#1.3. SpringBoot 三种模式)
[2.1. 交换机持久化](#2.1. 交换机持久化)
[2.2. 队列持久化](#2.2. 队列持久化)
[2.3. 消息持久化](#2.3. 消息持久化)
[2.4. 注意点](#2.4. 注意点)
一、消息确认机制
消息确认机制是 RabbitMQ 为保障消息从队列可靠投递到消费者而设计的核心机制。在默认的投递逻辑中,RabbitMQ 将消息发送给消费者之后,就会立即从内存或磁盘中移除这条消息,一旦消费者端出现处理异常、服务宕机等情况,消息就会彻底丢失。为了解决这个问题,RabbitMQ 引入了消息确认机制,让服务端可以感知消费者对消息的处理结果,再决定是删除消息还是重新投递,以此在不同可靠性要求的场景中平衡性能与数据安全。
1.1. autoAck 自动确认
自动确认是消息确认机制中最简单的一种模式,对应订阅队列时 autoAck 参数设置为 true 的场景。在该模式下,RabbitMQ 只要把消息投递到消费者的 TCP 连接中,就会自动将这条消息标记为已确认,随后直接从队列中删除,完全不会等待消费者的处理结果。这种模式的优势是投递速度快、服务端开销低,能够最大化消息吞吐量,适合对消息可靠性要求不高的场景,比如普通日志采集、非核心状态同步等。但它的风险也十分明显,如果消费者接收到消息后还没处理完就宕机,或者业务逻辑执行失败,消息会直接丢失,无法找回。
1.2. 手动确认
手动确认模式则是高可靠场景下的主流选择,对应 autoAck 参数设置为 false 的场景。在该模式下,RabbitMQ 将消息投递出去之后,不会立刻删除消息,而是一直等待消费者显式返回确认指令;如果对应消费者的连接断开,却始终没有收到确认信号,RabbitMQ 就会将这条消息重新放回队列头部,等待投递给下一个消费者,也有可能再次投递给原消费者。此时队列中的消息会被分为两类,一类是处于等待投递状态的 Ready 消息,另一类是已经投递但尚未收到确认的 Unacked 消息,二者的数量都可以在 RabbitMQ 的 Web 管理后台实时查看。
手动确认模式下,消费者可以根据业务处理结果,选择三种不同的应答方式。第一种是肯定确认 basicAck,接收 deliveryTag 和 multiple 两个参数,其中 deliveryTag 是每个通道独立维护的、单调递增的消息唯一编号,multiple 参数设为 true 时,可以一次性确认所有编号小于等于当前 deliveryTag 的消息,通过批量确认减少网络交互开销。第二种是否定确认 basicReject,同样基于 deliveryTag 定位消息,通过 requeue 参数控制拒绝后的行为,设为 true 时消息会重新进入队列等待重发,设为 false 时消息会被直接移除。第三种是批量否定确认 basicNack,在 basicReject 的基础上增加了批量能力,可以一次性拒绝多条消息,适合批量处理消息时统一返回失败结果的场景。
1.3. SpringBoot 三种模式
在 Spring AMQP 框架中,又对原生的消息确认机制做了封装,提供了三种更贴合 Spring 开发模式的确认策略。第一种是 NONE 模式,效果与原生自动确认一致,消息投递后立即确认,消费者处理失败时消息直接丢失。第二种是 AUTO 模式,也是 Spring Boot 的默认策略,框架会在消费者方法正常执行完成后自动提交确认,如果方法抛出未捕获的异常,则自动触发否定确认并让消息重入队列;这种模式不用开发者手写确认逻辑,使用简单,但如果业务逻辑持续抛出异常,消息会被不断重发,容易造成 Unacked 消息积压。第三种是 MANUAL 模式,要求开发者必须在代码中显式调用确认或否定确认方法,框架不会自动干预,开发者可以根据业务成功、失败、异常等不同场景灵活处理,可靠性和灵活性最高,适合复杂的核心业务场景。
java
spring:
application:
name: rabbit-extension-demo
rabbitmq:
addresses: amqp://用户名:密码@公网ip:5672/虚拟机
listener:
simple:
acknowledge-mode: none
java
package com.yang.rabbitextensionsdemo.constant;
public class Constants {
// 消息确认
public static final String ACK_QUEUE = "ack.queue";
public static final String ACK_EXCHANGE = "ack.exchange";
}
java
package com.yang.rabbitextensionsdemo.config;
import com.yang.rabbitextensionsdemo.constant.Constants;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitMQConfig {
// 消息确认
@Bean("ackQueue")
public Queue ackQueue() {
return QueueBuilder.durable(Constants.ACK_QUEUE).build();
}
@Bean("directExchange")
public DirectExchange directExchange() {
return ExchangeBuilder.directExchange(Constants.ACK_EXCHANGE).build();
}
@Bean("ackBinding")
public Binding ackBinding(@Qualifier("directExchange") DirectExchange directExchange, @Qualifier("ackQueue") Queue queue) {
return BindingBuilder.bind(queue).to(directExchange).with("ack");
}
}
java
package com.yang.rabbitextensionsdemo.controller;
import com.yang.rabbitextensionsdemo.constant.Constants;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/producer")
public class ProducerController {
@Autowired
private RabbitTemplate rabbitTemplate;
@RequestMapping("/ack")
public String ack() {
rabbitTemplate.convertAndSend(Constants.ACK_EXCHANGE, "ack", "consumer ack mode test...");
return "消息发送成功";
}
}
java
package com.yang.rabbitextensionsdemo.listener;
import com.rabbitmq.client.Channel;
import com.yang.rabbitextensionsdemo.constant.Constants;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class AckListener {
@RabbitListener(queues = Constants.ACK_QUEUE)
public void handMessage(Message message, Channel channel) throws Exception {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
// 消费者逻辑
System.out.printf("接收到消息: %s, deliveryTag: %d \n", new String(message.getBody(), "UTF-8"), message.getMessageProperties().getDeliveryTag());
System.out.println("业务逻辑处理");
int n = 3 / 0;
System.out.println("业务处理完成");
}
}
none 模式:


auto 模式:


java
@RabbitListener(queues = Constants.ACK_QUEUE)
public void handMessage(Message message, Channel channel) throws Exception {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
// 消费者逻辑
System.out.printf("接收到消息: %s, deliveryTag: %d \n", new String(message.getBody(), "UTF-8"), message.getMessageProperties().getDeliveryTag());
System.out.println("业务逻辑处理");
int n = 3 / 0;
System.out.println("业务处理完成");
// 肯定确认
channel.basicAck(deliveryTag, false);
} catch (Exception e) {
// 否定确认
channel.basicNack(deliveryTag, false, true);
}
}


实际选型和使用时,需要根据业务的可靠性要求和吞吐量需求平衡选择。对于日志、监控指标等允许少量丢失的数据,优先使用自动确认以获得更高的吞吐;对于订单、支付、账务等核心数据,必须使用手动确认或者 Spring 的 MANUAL 模式,确保消息处理成功后再确认。同时要注意避免常见的使用问题,比如忘记调用确认方法导致 Unacked 消息持续堆积、跨通道调用确认方法引发报错、过度使用单条确认导致网络开销过大等。另外,开启手动确认后,需要合理设计异常处理逻辑,对于业务逻辑本身导致的永久失败,不要设置 requeue 为 true 反复重发,而是应该转入死信队列做后续处理。
二、持久化
持久化是RabbitMQ用于解决服务端宕机数据丢失问题的核心机制。默认情况下,RabbitMQ运行过程中的交换机、队列以及消息都只保存在内存中,一旦服务进程退出、服务器崩溃或者主机重启,所有数据都会被清空。持久化的核心作用就是将关键的元数据和消息本身写入磁盘存储,当RabbitMQ服务恢复启动时,可以自动从磁盘中加载恢复之前的数据,尽可能降低异常停机带来的数据损失。
2.1. 交换机持久化
交换机持久化是持久化体系的第一层,针对的是交换机的元数据。它通过声明交换机时将durable参数设置为true实现,开启后交换机的类型、名称、持久化标记等配置信息会被保存到磁盘中。当RabbitMQ服务意外重启后,这些交换机会被自动重建,不需要应用重新声明,也不会因为交换机不存在导致生产者发送消息失败。由于交换机本身的元数据量非常小,持久化带来的性能开销几乎可以忽略,因此生产环境中长期使用的交换机通常都会开启持久化。如果不设置交换机持久化,重启后交换机元数据会直接丢失,所有依赖该交换机的消息路由都会失效。
java
// 持久化
public static final String PRES_QUEUE = "pres.queue";
public static final String PRES_EXCHANGE = "pres.exchange";
java
// 持久化
@Bean("presQueue")
public Queue preQueue() {
return QueueBuilder.nonDurable(Constants.PRES_QUEUE).build();
}
@Bean("presExchange")
public DirectExchange presExchange() {
return ExchangeBuilder.directExchange(Constants.PRES_EXCHANGE).durable(false).build();
}
@Bean("presBinding")
public Binding presBinding(@Qualifier("presExchange") Exchange exchange, @Qualifier("presQueue") Queue queue) {
return BindingBuilder.bind(queue).to(exchange).with("pres").noargs();
}
java
@RequestMapping("/pres")
public String pres() {
Message message = new Message("Presistent test...".getBytes(), new MessageProperties());
message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.NON_PERSISTENT);
rabbitTemplate.convertAndSend(Constants.PRES_EXCHANGE, "pres", message);
return "消息发送成功";
}


2.2. 队列持久化
队列持久化是第二层,针对队列本身的元数据。和交换机持久化类似,声明队列时将durable参数设为 true 即可开启,开启后队列的名称、属性、参数配置等信息会持久化到磁盘。需要特别注意的是,队列持久化只能保证队列结构在重启后依然存在,完全不能保证队列中存储的消息还能保留。如果只开启队列持久化而不处理消息持久化,服务重启之后队列虽然还在,但内部的消息会全部清空。反过来,如果只设置消息持久化而不持久化队列,重启后队列本身会被删除,消息失去了存储载体自然也会消失,因此单独设置消息持久化是没有意义的。日常开发中 Spring AMQP 的QueueBuilder.durable 方法默认就会开启队列持久化,很多开发者容易忽略这个默认行为,而如果使用 nonDurable 方法创建的队列,服务重启后就会直接消失。


2.3. 消息持久化
消息持久化是第三层,也是真正保证消息数据不丢失的核心。消息持久化需要将消息属性中的deliveryMode设置为2,也就是持久化模式,设置后的消息会被写入RabbitMQ的持久化存储中,服务重启后可以从磁盘完整恢复。原生 Java 客户端中可以直接使用封装好的MessageProperties.PERSISTENT_TEXT_PLAIN 来发送持久化消息,Spring AMQP中则需要手动设置 MessageProperties 的 deliveryMode为PERSISTENT。这里存在一个常见的认知误区,很多人以为 RabbitMQ 默认消息是非持久化的,实际上默认情况下消息会被视为持久化,除非队列本身被声明为非持久化,或者发送时显式将消息标记为非持久化。
2.4. 注意点
要实现服务重启后消息完整不丢失,必须同时满足交换机持久化、队列持久化、消息持久化三个条件,三者缺一不可。这也是很多初学者容易踩的坑,要么只设置了消息持久化,要么只开启了队列持久化,最后重启服务发现消息依然丢失,排查很久才发现是某一层没有配置持久化。三者的依赖关系是递进的:交换机持久化是基础,队列持久化是载体,消息持久化是最终目的,任何一层缺失都会导致整个持久化链路失效。
持久化并非没有代价,它最核心的成本是磁盘IO开销。写入磁盘的速度远低于写入内存,全量开启持久化会显著降低RabbitMQ的消息吞吐量,高并发场景下影响尤为明显。因此在实际应用中,需要在可靠性和性能之间做权衡,根据业务的重要程度分级处理。对于订单、支付、账务这类核心业务数据,必须开启完整的持久化保证数据安全;对于日志采集、状态同步、非核心通知这类允许少量丢失的场景,则可以关闭持久化来换取更高的吞吐量。
即便完整开启了三层持久化,也不能百分之百保证消息绝对不丢失,它依然存在两个层面的局限性。第一个层面在消费者端,如果消费者使用自动确认模式,RabbitMQ发出消息后就会立即删除,消费者还没来得及处理消息就宕机的话,消息依然会丢失,这个问题需要配合手动消息确认机制来解决。第二个层面在服务端落盘时机,RabbitMQ不会为每条消息都执行强制刷盘操作,消息可能先暂存在操作系统的页缓存中,还没真正写入物理磁盘,如果这时候节点突然宕机,这部分消息就会丢失。针对这个风险,一方面可以引入仲裁队列集群,通过主从副本提升高可用性,降低单节点宕机的影响;另一方面可以配合发送方确认机制,等消息完成持久化落盘后再给生产者返回确认,让发送端明确感知消息的存储状态。
整体来说,持久化是RabbitMQ消息可靠性的基础能力,但不是孤立的解决方案,它需要和消息确认机制、发送方确认、死信队列等特性相互配合,才能构建出端到端的完整可靠消息体系。