消息从生产者到消费者的每一步都可能导致消息丢失:
-
发送消息时丢失:
-
生产者发送消息时连接MQ失败
-
生产者发送消息到达MQ后未找到
Exchange -
生产者发送消息到达MQ的
Exchange后,未找到合适的Queue -
消息到达MQ后,处理消息的进程发生异常
-
-
MQ导致消息丢失:
- 消息到达MQ,保存到队列后,尚未消费就突然宕机
-
消费者处理消息时:
-
消息接收后尚未处理突然宕机
-
消息接收后处理过程中抛出异常
-
综上,我们要解决消息丢失问题,保证MQ的可靠性,就必须从3个方面入手:
-
确保生产者一定把消息发送到MQ
-
确保MQ不会将消息弄丢
-
确保消费者一定要处理消息
代码git地址:https://gitee.com/zhang_xiaosong/springboot/tree/master/springboot-rabbitmq
一:保证消息不丢失-发送者的可靠性
1.1:生产者重连
1.1.1:它解决什么问题
当生产者与 RabbitMQ Broker 之间的网络出现抖动、Broker 重启或集群发生主备切换时,TCP 连接可能会断开。如果没有重连机制,生产者将无法继续发送消息,且需要人工介入重启应用。生产者重连能让客户端自动检测连接中断并尝试重新建立连接,恢复生产者的发送能力。
1.1.2:RabbitMQ Java 客户端的内置机制
-
自动恢复(Automatic Recovery):从 RabbitMQ Java Client 3.6.0 开始默认启用。它会自动重连并恢复:
-
已声明的交换器、队列、绑定(若声明是幂等的)
-
消费者(但生产者通常不关心消费者恢复)
-
注意 :处于
waitForConfirms()或ConfirmListener中未确认的消息不会自动重新发送,需要应用层自行处理。
-
-
拓扑恢复(Topology Recovery) :重连后,客户端会重新声明之前存在的交换器、队列和绑定(前提是这些组件声明时使用了
AutorecoveringConnection)。
1.1.3:Spring Boot 中的配置
修改publisher模块的application.yaml文件,添加下面的内容:
bash
spring:
rabbitmq:
# RabbitMQ 服务端地址
host: 192.168.1.100
# RabbitMQ 服务端端口
port: 5672
# ========== 连接与自动恢复配置 ==========
# 建立 TCP 连接的超时时间(毫秒),超过此时间未连接成功则抛出异常
connection-timeout: 30000
# 心跳超时(秒),用于检测连接是否存活。若60秒内没有发送或接收任何数据,Broker 会判定连接断开并关闭,客户端随后会触发重连
requested-heartbeat: 60
# ========== 发布确认(必需,保证消息不丢失) ==========
# 发布确认类型:correlated 表示开启异步确认模式,生产者可以收到 Broker 的 ack/nack 回调
publisher-confirm-type: correlated
# 是否启用返回回调:当消息已到达交换机,但无法路由到任何队列时,Broker 会通过此机制将消息返回给生产者
publisher-returns: true
# RabbitTemplate 的默认配置
template:
# mandatory 必须设置为 true,配合 publisher-returns 使用。若为 false,路由失败的消息会被 Broker 直接丢弃,不会触发返回回调
mandatory: true
# ========== 应用层发送重试(可选,非连接层重试) ==========
template:
# RabbitTemplate 发送消息时的重试配置(注意:这里是缩进属于 template 下的另一个属性,实际 YAML 中可合并)
retry:
# 是否启用发送重试(默认 false)。开启后,当发送操作因网络异常等临时故障失败时,RabbitTemplate 会进行本地重试
enabled: true
# 第一次重试前的初始等待时间(毫秒)
initial-interval: 1000
# 最大重试次数(不包括首次发送)
max-attempts: 3
# 退避倍数,每次重试等待时间 = 上一次等待时间 * multiplier
multiplier: 1.5
重要 :
spring.rabbitmq.template.retry配置的是RabbitTemplate发送消息时的重试次数(发送失败后重试),这和连接层的自动恢复是两回事。
停掉RabbitMQ服务:
然后测试发送一条消息,会发现会每隔1秒重试1次,总共重试了3次。消息发送的超时重试机制配置成功了!

注意 :当网络不稳定的时候,利用重试机制可以有效提高消息发送的成功率。不过SpringAMQP提供的重试机制是阻塞式的重试,也就是说多次重试等待的过程中,当前线程是被阻塞的。 如果对于业务性能有要求,建议禁用重试机制。如果一定要使用,请合理配置等待时长和重试次数,当然也可以考虑使用异步线程来执行发送消息的代码。
1.1.4:生产者重连的局限性
-
只能恢复连接和元数据,不能恢复未确认的消息 :假如发送了一条消息,Broker 还未返回确认(
ack)时网络断开,客户端重连成功后,那条消息的状态就丢失了。生产者需要自己负责重新发送。 -
可能导致重复消息:如果你在重试逻辑中重新发送了上一批未确认的消息,Broker 可能已经实际收到并处理了(只是确认帧丢失),这会造成消息重复。
因此,生产者重连必须与生产者确认配合使用。
1.2:生产者确认
1.2.1:它解决什么问题?
确认 Broker 确实收到了消息 ,并且(如果消息和队列都是持久化的)已经写入磁盘。它解决了网络传输过程中消息丢失、Broker 内部错误导致消息未被存储的问题。
一般情况下,只要生产者与MQ之间的网路连接顺畅,基本不会出现发送消息丢失的情况,因此大多数情况下我们无需考虑这种问题。 不过,在少数情况下,也会出现消息发送到MQ之后丢失的现象,比如:
-
MQ内部处理消息的进程发生了异常
-
生产者发送消息到达MQ后未找到
Exchange -
生产者发送消息到达MQ的
Exchange后,未找到合适的Queue,因此无法路由
针对上述情况,RabbitMQ提供了生产者消息确认机制,包括**Publisher Confirm和Publisher Return** 两种。在开启确认机制的情况下,当生产者发送消息给MQ后,MQ会根据消息处理的情况返回不同的回执。
1.2.2:工作流程

总结如下:
-
当消息投递到MQ,但是路由失败时,通过Publisher Return返回异常信息,同时返回ack的确认信息,代表投递成功
-
临时消息投递到了MQ,并且入队成功,返回ACK,告知投递成功
-
持久消息投递到了MQ,并且入队完成持久化,返回ACK ,告知投递成功
-
其它情况都会返回NACK,告知投递失败
其中ack和nack属于Publisher Confirm 机制,ack是投递成功;nack是投递失败。而return则属于Publisher Return机制。 默认两种机制都是关闭状态,需要通过配置文件来开启。
1.2.3: 工作原理
-
生产者将信道设置为
confirm模式(channel.confirmSelect())。 -
每条消息被 Broker 接收后,会返回一个
ack(或nack)给生产者,其中包含消息的唯一递送标签(deliveryTag)。 -
生产者可以同步 等待(
waitForConfirms())或异步 接收回调(addConfirmListener)。
1.2.4:Spring Boot 配置与使用
bash
spring:
rabbitmq:
publisher-confirm-type: correlated # 启用异步确认
publisher-returns: true # 同时开启消息路由失败返回
template:
mandatory: true # 必须设为true,否则路由失败的消息不会返回
配置详解
publisher-confirm-type: correlated
这个配置是消息可靠性的第一道关卡,它开启并设定了生产者确认(Publisher Confirm) 模式,用以确认消息是否成功从生产者发送到RabbitMQ的交换机(Exchange)。
RabbitMQ 提供了三种模式,它们的区别如下:
| 配置值 | 确认方式 | 工作机制 | 特点与建议 |
|---|---|---|---|
none |
无确认 | 默认值,不开启任何确认机制。 | 消息可能丢失,不建议在生产环境使用。 |
correlated |
异步回调(推荐) | 发送时可以关联一个CorrelationData对象。Broker收到消息后会异步回调通知成功(ack )或失败(nack)。 |
性能好、不阻塞,是生产环境的推荐配置。 |
simple |
同步等待 | 发送后线程同步阻塞等待Broker的确认结果,直到超时或失败。 | 性能较差 ,容易造成线程阻塞,不推荐使用。 |
简单来说,correlated模式就是让生产者发送消息后继续干别的事,当RabbitMQ处理完消息后,会通过回调函数"主动打电话"通知结果。
publisher-returns: true
这个配置与mandatory参数配合,构成了第二道保障,用于确认消息是否成功从交换机路由到队列(Queue) 。当开启后,如果消息到达了交换机,但因为路由键(Routing Key)匹配失败等原因,未能投递到任何队列,RabbitMQ就会通过ReturnCallback将消息"退回"给生产者。
template.mandatory: true
这是决定路由失败消息命运的关键开关。当设置为true时,如果消息无法路由到任何队列,RabbitMQ必须 执行Basic.Return命令将消息返回给生产者,触发ReturnCallback。
如果设置为false,RabbitMQ会直接静默丢弃这些无法路由的消息,让你在不知不觉中丢失重要数据。
🚀 核心机制与流程
整个确认流程可以分为两个清晰的阶段:
-
ConfirmCallback:负责第一阶段(生产者 -> 交换机)的确认。 -
ReturnCallback:负责第二阶段(交换机 -> 队列)的确认。
这两个回调机制协同工作,形成了一个完整的链路:
-
生产者发送消息。
-
ConfirmCallback被触发:-
ack = true:消息已成功到达交换机。 -
ack = false:消息未能到达交换机(例如,交换机名称错误)。
-
-
ReturnCallback被触发 (仅在mandatory=true时):-
当
ack = true但消息无法路由到任何队列时触发,返回具体原因。 -
如果消息成功路由到队列,则不会触发此回调。
-
1.2.5 :生产者确认的局限性
-
只保证 Broker 接收到了消息,不保证消费者已经处理(那是消费者确认的范畴)。
-
如果 Broker 在确认之前宕机,生产者会收到
nack或超时,需要重新发送。 -
开启确认模式会略微降低吞吐量(但远好于事务)。
1.3:两者如何协同工作?
| 机制 | 作用范围 | 触发场景 | 保证效果 |
|---|---|---|---|
| 生产者重连 | 连接层面 | 网络中断、Broker 重启 | 自动恢复连接,避免人工介入;但不能恢复消息 |
| 生产者确认 | 消息层面 | 每条消息发送后 | 确认 Broker 已接收并持久化消息 |
典型的生产者可靠发送流程:
-
开启自动重连,确保连接断开后能重新建立。
-
开启生产者确认模式。
-
发送消息时携带全局唯一 ID(
CorrelationData)。 -
异步监听
ConfirmCallback:-
收到
ack→ 更新本地消息状态为"成功"。 -
收到
nack或超时 → 进入重试队列或本地消息表。
-
-
同时监听
ReturnCallback(处理无法路由到队列的消息)。 -
如果网络闪断导致确认丢失,生产者重连后,那些未收到确认的消息需要由业务层重新发送(通常配合本地消息表,定时扫描并重试)。
1.4:实战
1.4.1:yml配置
先在publisher模块的yml配置文件中新增以下配置
bash
logging:
pattern:
dateformat: MM-dd HH:mm:ss:SSS
spring:
rabbitmq:
host: 127.0.0.1
port: 5672
virtual-host: /
username: admin
password: admin123
# ========== 连接与自动恢复配置 ==========
# 建立 TCP 连接的超时时间(毫秒),超过此时间未连接成功则抛出异常
connection-timeout: 30000
# 心跳超时(秒),用于检测连接是否存活。若60秒内没有发送或接收任何数据,Broker 会判定连接断开并关闭,客户端随后会触发重连
requested-heartbeat: 60
# ========== 发布确认(必需,保证消息不丢失) ==========
# 发布确认类型:correlated 表示开启异步确认模式,生产者可以收到 Broker 的 ack/nack 回调
publisher-confirm-type: correlated
# 是否启用返回回调:当消息已到达交换机,但无法路由到任何队列时,Broker 会通过此机制将消息返回给生产者
publisher-returns: true
# ========== 应用层发送重试(可选,非连接层重试) ==========
template:
# mandatory 必须设置为 true,配合 publisher-returns 使用。若为 false,路由失败的消息会被 Broker 直接丢弃,不会触发返回回调
mandatory: true
# RabbitTemplate 发送消息时的重试配置(注意:这里是缩进属于 template 下的另一个属性,实际 YAML 中可合并)
retry:
# 是否启用发送重试(默认 false)。开启后,当发送操作因网络异常等临时故障失败时,RabbitTemplate 会进行本地重试
enabled: true
# 第一次重试前的初始等待时间(毫秒)
initial-interval: 1000
# 最大重试次数(不包括首次发送)
max-attempts: 3
# 退避倍数,每次重试等待时间 = 上一次等待时间 * multiplier
multiplier: 1.5
1.4.2:定义ReturnCallback
每个RabbitTemplate只能配置一个ReturnCallback,因此我们可以在配置类中统一设置。我们在publisher模块定义一个配置类:
setReturnsCallback 回调函数,会在消息成功到达交换机(Exchange),但是交换机无法将它路由到任何队列(Queue)时被调用。
java
/**
* @description: ReturnCallback配置类
* @author: zgs
* @date: 2026/4/22 15:42
*/
@Configuration
@Slf4j
public class MqReturnCallBackConfig implements ApplicationContextAware {
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
RabbitTemplate rabbitTemplate = applicationContext.getBean(RabbitTemplate.class);
rabbitTemplate.setReturnsCallback(returned -> {
log.info("消息无法路由: exchange={}, routingKey={}, replyCode={}, replyText={}, message={}",
returned.getExchange(), returned.getRoutingKey(),
returned.getReplyCode(), returned.getReplyText(), returned.getMessage().toString());
});
}
}
1.4.3:定义ConfirmCallback
测试使用的交换机与队列如下

testPublisherConfirm代码如下,先故意写错交换机,然后执行查看控制台输出日志
java
@Test
public void testPublisherConfirm() {
String exchangeName = "zgs.direct11111";
String routingKey = "red";
String message = "hello, publisher confirm";
// 1. 创建 CorrelationData 并设置唯一 ID
String msgId = UUID.randomUUID().toString();
CorrelationData cd = new CorrelationData(msgId);
cd.getFuture().addCallback(new ListenableFutureCallback<CorrelationData.Confirm>() {
@Override
public void onFailure(Throwable ex) {
log.error("发送消息异常, msgId: {}, ex: {}", msgId, ex.getMessage());
}
@Override
public void onSuccess(CorrelationData.Confirm result) {
if (result.isAck()) {
log.info("消息发送成功, msgId: {}", msgId);
} else {
log.error("消息发送失败, msgId: {}, reason: {}", msgId, result.getReason());
}
}
});
rabbitTemplate.convertAndSend(exchangeName, routingKey, message, cd);
}
控制台输出如下:

testPublisherConfirm代码如下,交换机写对,故意写出路由键查看控制台输出
java
@Test
public void testPublisherConfirm() {
String exchangeName = "zgs.direct";
String routingKey = "red111";
String message = "hello, publisher confirm";
// 1. 创建 CorrelationData 并设置唯一 ID
String msgId = UUID.randomUUID().toString();
CorrelationData cd = new CorrelationData(msgId);
cd.getFuture().addCallback(new ListenableFutureCallback<CorrelationData.Confirm>() {
@Override
public void onFailure(Throwable ex) {
log.error("发送消息异常, msgId: {}, ex: {}", msgId, ex.getMessage());
}
@Override
public void onSuccess(CorrelationData.Confirm result) {
if (result.isAck()) {
log.info("消息发送成功, msgId: {}", msgId);
} else {
log.error("消息发送失败, msgId: {}, reason: {}", msgId, result.getReason());
}
}
});
rabbitTemplate.convertAndSend(exchangeName, routingKey, message, cd);
}
控制台输出如下,说明触发了return callback 。但是消息其实是发送成功了的,只是路由到具体队列的时候失败了。

注意: 开启生产者确认比较消耗MQ性能,一般不建议开启。而且大家思考一下触发确认的几种情况:
-
路由失败:一般是因为RoutingKey错误导致,往往是编程导致
-
交换机名称错误:同样是编程错误导致
-
MQ内部故障:这种需要处理,但概率往往较低。因此只有对消息可靠性要求非常高的业务才需要开启,而且仅仅需要开启ConfirmCallback处理nack就可以了。
二:保证消息不丢失-MQ的可靠性
消息到达MQ以后,如果MQ不能及时保存,也会导致消息丢失,所以MQ的可靠性也非常重要。
为了提升性能,默认情况下MQ的数据都是在内存存储的临时数据,重启后就会消失。为了保证数据的可靠性,必须配置数据持久化,包括:
-
交换机持久化
-
队列持久化
-
消息持久化
2.1:第一道防线:消息持久化
这是最基础也最核心的保障,目标是在 Broker 重启后,消息不丢失。它的实现需要交换机、队列、消息三者配合:
2.1.1:交换机持久化
在声明交换机时,通过设置 durable=true 参数来实现。这样,RabbitMQ 重启后无需重新创建交换机,绑定的路由规则也会随之恢复。

代码中声明交换机持久化
java
@Bean
public DirectExchange orderExchange() {
// durable: 是否持久化 第二个参数就表示持久化
return new DirectExchange("order.direct", true, false);
}
2.1.2:队列持久化
同样在声明队列时,设置 durable=true 参数。这确保了队列本身的元数据(如名称、绑定关系)在 Broker 重启后依然存在。

代码中声明队列持久化
java
@Bean
public Queue orderQueue() {
// durable: 是否持久化
return QueueBuilder.durable("order.queue")
.maxLength(10000)
.deadLetterExchange("order.dlx")
.build();
}
@Bean
public Queue paymentQueue() {
// durable: 是否持久化 第二个参数就表示这个队列持久化
return new Queue("payment.queue", true);
}
2.1.3:消息持久化
在发布消息时,将消息的投递模式(deliveryMode)设置为 2,即标记为持久化消息。此时,Broker 会将消息内容写入磁盘。

代码中声明发送的消息持久化
java
/**
* 发送消息持久化 方式一
*
* @author: zgs
* @date: 2026/4/22 16:50
*/
@Test
public void testDurableSendMessage1() {
String exchangeName = "zgs.direct";
String routingKey = "red";
MessageProperties properties = new MessageProperties();
// 设置投递模式为持久化(2 = PERSISTENT)
properties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
// 也可以直接写数字:properties.setDeliveryMode(2);
Message message = new Message("hello, 持久化消息".getBytes(), properties);
rabbitTemplate.send(exchangeName, routingKey, message);
}
/**
* 发送消息持久化 方式二
*
* @author: zgs
* @date: 2026/4/22 16:50
*/
@Test
public void testDurableSendMessage2() {
String exchangeName = "zgs.direct";
String routingKey = "red";
rabbitTemplate.convertAndSend(exchangeName, routingKey, "hello, 持久化消息",
(Message message) -> {
message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
return message;
});
}
/**
* 发送消息持久化 方式三
*
* @author: zgs
* @date: 2026/4/22 16:50
*/
@Test
public void testDurableSendMessage3() {
/**
* Spring AMQP 的 RabbitTemplate 默认使用的 SimpleMessageConverter
* 会将 String 或 Serializable 对象构建为 Message,
* 此时 deliveryMode 默认为 PERSISTENT。但为了代码清晰和避免版本差异,建议显式设置。
*/
}
📌 重点提醒:
缺一不可:这三者必须同时开启,才能保证消息在服务端重启时不丢失。任何一环的缺失,都会导致消息丢失。
并非绝对安全 :持久化并不能保证消息 100% 不丢失。为了性能,RabbitMQ 采用批量异步刷盘,并非实时。如果在刷盘前 Broker 宕机,已标记为持久化的消息仍可能丢失。因此,它必须和上一讲提到的生产者确认机制配合使用。
性能权衡:持久化会带来额外的磁盘 I/O 开销,会一定程度地影响消息吞吐量和增加延迟,需要在可靠性与性能之间做权衡。
2.2:第二道防线:高可用队列
持久化解决了时间 上的问题(重启后恢复),而高可用队列则解决了空间上的问题(单点故障)。当集群中的一个节点宕机时,高可用机制确保消息队列依然可用,避免消息丢失。
RabbitMQ 提供了两种实现方案:传统的镜像队列 和更先进的仲裁队列。
2.2.1:镜像队列 (Mirrored Queues)
这是早期的方案。它会在集群的多个节点上创建主队列的副本(镜像)。所有读写操作都通过主队列完成,主队列再将数据同步给镜像。
⚠️ 局限与风险:
-
同步阻塞:在镜像队列的某些版本实现中,消息同步是阻塞的。特别是当一个宕机的节点重新加入集群时,需要将主队列中的大量消息同步给它,这个同步过程会导致整个队列在此期间不可用。
-
数据丢失风险:如果主节点在消息尚未同步给所有镜像前宕机,这部分未同步的消息就有可能丢失。
-
已逐步淘汰 :由于其上述缺陷,自 RabbitMQ 3.9 版本起,镜像队列已被标记为已弃用,并计划在未来版本中移除。官方文档强烈建议新项目不要再使用镜像队列。
2.2.2:仲裁队列 (Quorum Queues)
这是 RabbitMQ 3.8+ 版本引入的官方推荐的高可用队列,旨在彻底解决镜像队列的顽疾。它的核心是 Raft 共识算法。
-
多数派确认 :消息必须被集群中超过半数的副本节点成功写入后,才会向生产者返回确认。这保证了数据的强一致性,只要集群中多数节点正常,消息就不会丢失。
-
故障自动转移:当领导者(Leader)节点宕机时,其余副本会通过 Raft 协议自动、快速地选举出新的领导者,且选举过程对客户端透明。
-
数据安全优先:仲裁队列以数据安全为首要设计目标,所有消息默认都是持久化的。
| 特性 | 镜像队列 (Mirrored Queues) | 仲裁队列 (Quorum Queues) |
|---|---|---|
| 算法 | 自研链式复制 | Raft 共识算法 |
| 消息确认 | 主节点确认 | 多数派确认 |
| 故障转移 | 可能丢失数据 | 自动且安全 |
| 同步影响 | 阻塞队列 | 非阻塞 |
| 状态 | 已弃用 (Deprecated) | 官方推荐 |
| 支持版本 | 所有版本 | RabbitMQ 3.8+ |
对于所有对数据安全有要求的生产环境,应直接使用仲裁队列,放弃已淘汰的镜像队列。
java
@Bean
public Queue myQuorumQueue() {
return QueueBuilder.durable("my.quorum.queue")
.quorum() // 设置为仲裁队列,内部自动 durable 且消息持久化
.build();
}
2.3: 第三道防线:惰性队列 (Lazy Queues)
惰性队列主要应对的是消息堆积风险。在默认模式下,RabbitMQ 会尽可能将消息存储在内存中以获得高性能。但一旦消费者故障或处理变慢,大量消息积压,可能导致内存耗尽,进而触发"换页"操作,严重时甚至导致节点崩溃,引发消息丢失。
惰性队列的设计目标是"支持更长的队列",它能更好地处理大量消息堆积。
-
工作原理 :惰性队列会将接收到的消息几乎直接写入磁盘,只有在消费者需要消费时,才会从磁盘加载到内存。
-
优势 :了牺牲了一定的吞吐量,换取极其稳定的内存占用 和处理大量堆积消息的能力。即使有百万条消息积压,服务端的内存使用率也保持平稳。
-
启用方式 :在声明队列时,通过设置
x-queue-mode参数为lazy即可开启。
从 RabbitMQ 3.12 版本开始,惰性队列已成为所有队列的默认模式。
2.3.1:设置队列为Lazy Queues

代码中声明队列为LazyQueue
java
@Bean
public Queue myLazyQueue() {
return QueueBuilder.durable("my.lazy.queue")
.lazy() // 声明队列为惰性队列
.build();
}
//在注解中声明队列为惰性队列
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = "message.convert.queue2", durable = "true",arguments = @Argument(name = "x-queue-mode", value = "lazy")),
exchange = @Exchange(value = "message.convert.exchange", type = ExchangeTypes.FANOUT),
key = ""
))
2.3.2:LazyQueue原理
惰性队列(Lazy Queue)的核心原理可以概括为**"空间换稳定"**:它主动放弃了内存缓存带来的性能优势,通过将消息尽可能早地写入磁盘,来换取系统在面对海量消息积压时的稳定性和可靠性。
🧠 工作原理与设计目标
传统队列的"Page Out"问题
-
工作方式:传统队列为追求低延迟,会优先将消息保存在内存缓存中,随后才异步写入磁盘。同时,消息也会写入磁盘以实现持久化。
-
核心问题 :当消息大量积压时,RabbitMQ 会触发内存预警,执行 "Page Out" 操作,将内存中的消息换页到磁盘。这个 I/O 密集型操作会阻塞队列,导致其无法处理新消息,影响生产者和集群中其他队列。
惰性队列的解决方案
惰性队列从 RabbitMQ 3.6.0 开始引入,旨在从根本上解决上述问题。其设计目标是支持包含数百万条消息的超长队列,避免因消费者故障或突然的消息高峰导致的内存溢出和性能崩溃。
它的工作方式颠覆了传统模式:
-
直接落盘 (Direct-to-Disk) :消息到达队列后,会立即被写入磁盘上的文件系统,完全消除了消息在内存中的缓存阶段。因此,其内存占用极低,并且彻底消除了因 "Page Out" 导致的队列阻塞问题。
-
按需加载 (Load-on-Consumption):消息只有在消费者请求时,才会从磁盘加载到内存中,实现了"懒加载"。
这种机制显著降低了内存压力,避免了频繁的垃圾回收,即使在大规模积压下,系统性能依然可以保持稳定。