05:RabbitMq-高级特性

消息从生产者到消费者的每一步都可能导致消息丢失:

  • 发送消息时丢失:

    • 生产者发送消息时连接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 ConfirmPublisher Return** 两种。在开启确认机制的情况下,当生产者发送消息给MQ后,MQ会根据消息处理的情况返回不同的回执

1.2.2:工作流程

总结如下:

  • 当消息投递到MQ,但是路由失败时,通过Publisher Return返回异常信息,同时返回ack的确认信息,代表投递成功

  • 临时消息投递到了MQ,并且入队成功,返回ACK,告知投递成功

  • 持久消息投递到了MQ,并且入队完成持久化,返回ACK ,告知投递成功

  • 其它情况都会返回NACK,告知投递失败

其中acknack属于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:负责第二阶段(交换机 -> 队列)的确认。

这两个回调机制协同工作,形成了一个完整的链路:

  1. 生产者发送消息。

  2. ConfirmCallback 被触发

    • ack = true:消息已成功到达交换机。

    • ack = false:消息未能到达交换机(例如,交换机名称错误)。

  3. ReturnCallback 被触发 (仅在mandatory=true时):

    • ack = true但消息无法路由到任何队列时触发,返回具体原因。

    • 如果消息成功路由到队列,则不会触发此回调。

1.2.5 :生产者确认的局限性

  • 只保证 Broker 接收到了消息,不保证消费者已经处理(那是消费者确认的范畴)。

  • 如果 Broker 在确认之前宕机,生产者会收到 nack 或超时,需要重新发送。

  • 开启确认模式会略微降低吞吐量(但远好于事务)。

1.3:两者如何协同工作?

机制 作用范围 触发场景 保证效果
生产者重连 连接层面 网络中断、Broker 重启 自动恢复连接,避免人工介入;但不能恢复消息
生产者确认 消息层面 每条消息发送后 确认 Broker 已接收并持久化消息

典型的生产者可靠发送流程:

  1. 开启自动重连,确保连接断开后能重新建立。

  2. 开启生产者确认模式。

  3. 发送消息时携带全局唯一 ID(CorrelationData)。

  4. 异步监听 ConfirmCallback

    • 收到 ack → 更新本地消息状态为"成功"。

    • 收到 nack 或超时 → 进入重试队列或本地消息表。

  5. 同时监听 ReturnCallback(处理无法路由到队列的消息)。

  6. 如果网络闪断导致确认丢失,生产者重连后,那些未收到确认的消息需要由业务层重新发送(通常配合本地消息表,定时扫描并重试)。

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 开始引入,旨在从根本上解决上述问题。其设计目标是支持包含数百万条消息的超长队列,避免因消费者故障或突然的消息高峰导致的内存溢出和性能崩溃。

它的工作方式颠覆了传统模式:

  1. 直接落盘 (Direct-to-Disk) :消息到达队列后,会立即被写入磁盘上的文件系统,完全消除了消息在内存中的缓存阶段。因此,其内存占用极低,并且彻底消除了因 "Page Out" 导致的队列阻塞问题。

  2. 按需加载 (Load-on-Consumption):消息只有在消费者请求时,才会从磁盘加载到内存中,实现了"懒加载"。

这种机制显著降低了内存压力,避免了频繁的垃圾回收,即使在大规模积压下,系统性能依然可以保持稳定。


三:保证消息不丢失-消费者的可靠性

四:延迟消息

相关推荐
yaoyouzhong8 小时前
RabbitMQ HAProxy 负载均衡
rabbitmq·负载均衡·ruby
gududexiao9 小时前
RabbitMQ 的介绍与使用
分布式·rabbitmq·ruby
Kristrina10 小时前
RabbitMQ高级特性----生产者确认机制
分布式·rabbitmq
weixin_4196583110 小时前
RabbitMQ 介绍
分布式·rabbitmq
iOS妖狐小北10 小时前
RabbitMQ之交换机
分布式·rabbitmq·ruby
unDl IONA11 小时前
Linux安装RabbitMQ
linux·运维·rabbitmq
weyyhdke11 小时前
RabbitMQ 集群部署方案
分布式·rabbitmq·ruby
MoFe113 小时前
【.net core】【watercloud】处理rabbitmq类初始化时获取系统已注入的数据库连接问题(调用已注入服务)
数据库·rabbitmq·.netcore
lzqk1200013 小时前
RabbitMQ 客户端 连接、发送、接收处理消息
分布式·rabbitmq·ruby