RabbitMQ如何保证队列里的消息99.99%被消费?

本文探讨了RabbitMQ如何通过消息确认机制确保消息被消费者成功消费,避免因消费者故障导致的消息丢失。介绍了显式Ack模式的实现方式,以及如何通过代码实现消息的手动确认。

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

RabbitMQ如何保证队列里的消息99.99%被消费?

2019年06月27日 19:51:40 过客啊啊 阅读数 30

1. 本篇概要

其实,还有1种场景需要考虑:当消费者接收到消息后,还没处理完业务逻辑,消费者挂掉了,那消息也算丢失了?,比如用户下单,订单中心发送了1个消息到RabbitMQ里的队列,积分中心收到这个消息,准备给这个下单的用户增加20积分,但积分还没增加成功呢,积分中心自己挂掉了,导致数据出现问题。

那么如何解决这种问题呢?

为了保证消息被消费者成功的消费,RabbitMQ提供了消息确认机制(message acknowledgement),本文主要讲解RabbitMQ中,如何使用消息确认机制来保证消息被消费者成功的消费,避免因为消费者突然宕机而引起的消息丢失。

2. 开启显式Ack模式

我们开启一个消费者的代码是这样的:

// 创建队列消费者
com.rabbitmq.client.Consumer consumer = new DefaultConsumer(channel) {
    @Override
    public void handleDelivery(String consumerTag, Envelope envelope,
                               AMQP.BasicProperties properties, byte[] body) throws IOException {
        String message = new String(body, "UTF-8");
        System.out.println("Received Message '" + message + "'");
    }
};
channel.basicConsume(QUEUE_NAME, true, consumer);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

这里的重点是channel.basicConsume(QUEUE_NAME, true, consumer);方法的第2个参数,让我们先看下basicConsume()的源码:

public String basicConsume(String queue, boolean autoAck, Consumer callback) throws IOException {
    return this.basicConsume(queue, autoAck, "", callback);
}
  • 1
  • 2
  • 3

这里的autoAck参数指的是是否自动确认,如果设置为ture,RabbitMQ会自动把发送出去的消息置为确认,然后从内存(或者磁盘)中删除,而不管消费者接收到消息是否处理成功;如果设置为false,RabbitMQ会等待消费者显式的回复确认信号后才会从内存(或者磁盘)中删除。

建议将autoAck设置为false,这样消费者就有足够的时间处理消息,不用担心处理消息过程中消费者宕机造成消息丢失。

此时,队列里的消息就分成了2个部分:

  1. 等待投递给消费者的消息(下图中的Ready部分)
  2. 已经投递给消费者,但是还没有收到消费者确认信号的消息(下图中的Unacked部分)

如果RabbitMQ一直没有收到消费者的确认信号,并且消费此消息的消费者已经断开连接,则RabbitMQ会安排该消息重新进入队列,等待投递给下一个消费者,当然也有可能还是原来的那个消费者。

RabbitMQ不会为未确认的消息设置过期时间,它判断此消息是否需要重新投递给消费者的唯一依据是消费该消息的消费者连接是否已经断开,这么设计的原因是RabbitMQ允许消费者消费一条消息的时间可以很久很久。

为了便于理解,我们举个具体的例子,生产者的话的我们延用上文中的DurableProducer:

package com.zwwhnly.springbootaction.rabbitmq.durable;

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class DurableProducer {
    private final static String EXCHANGE_NAME = "durable-exchange";
    private final static String QUEUE_NAME = "durable-queue";

    public static void main(String[] args) throws IOException, TimeoutException {
        // 创建连接
        ConnectionFactory factory = new ConnectionFactory();
        // 设置 RabbitMQ 的主机名
        factory.setHost("localhost");
        // 创建一个连接
        Connection connection = factory.newConnection();
        // 创建一个通道
        Channel channel = connection.createChannel();
        // 创建一个Exchange
        channel.exchangeDeclare(EXCHANGE_NAME, "direct", true);
        channel.queueDeclare(QUEUE_NAME, true, false, false, null);
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");

        // 发送消息
        String message = "durable exchange test";
        AMQP.BasicProperties props = new AMQP.BasicProperties().builder().deliveryMode(2).build();
        channel.basicPublish(EXCHANGE_NAME, "", props, message.getBytes());

        // 关闭频道和连接
        channel.close();
        connection.close();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

然后新建一个消费者AckConsumer类:

package com.zwwhnly.springbootaction.rabbitmq.ack;

import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class AckConsumer {
    private final static String QUEUE_NAME = "durable-queue";

    public static void main(String[] args) throws IOException, TimeoutException {
        // 创建连接
        ConnectionFactory factory = new ConnectionFactory();
        // 设置 RabbitMQ 的主机名
        factory.setHost("localhost");
        // 创建一个连接
        Connection connection = factory.newConnection();
        // 创建一个通道
        Channel channel = connection.createChannel();
        // 创建队列消费者
        com.rabbitmq.client.Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope,
                                       AMQP.BasicProperties properties, byte[] body) throws IOException {
                String message = new String(body, "UTF-8");
                int result = 1 / 0;
                System.out.println("Received Message '" + message + "'");
            }
        };
        channel.basicConsume(QUEUE_NAME, true, consumer);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

我们先将autoAck参数设置为ture,即自动确认,并在消费消息时故意写个异常,然后先运行生产者客户端将消息写入队列中,然后运行消费者客户端,发现消息未消费成功但是却消失了:

然后我们将autoAck设置为false:

channel.basicConsume(QUEUE_NAME, false, consumer);
  • 1

再次运行生产者客户端将消息写入队列中,然后运行消费者客户端,此时虽然消费者客户端仍然代码异常,但是消息仍然在队列中:

然后我们删除掉消费者客户端中的异常代码,重新启动消费者客户端,发现消息消费成功了,但是消息一直未Ack:

在这里插入图片描述

在这里插入图片描述

手动停掉消费者客户端,发现消息又到了Ready状态,准备重新投递:

在这里插入图片描述

之所以消费掉消息,却一直还是Unacked状态,是因为我们没在代码中添加显式的Ack代码:

String message = new String(body, "UTF-8");
//int result = 1 / 0;
System.out.println("Received Message '" + message + "'");

long deliveryTag = envelope.getDeliveryTag();
channel.basicAck(deliveryTag, false);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

deliveryTag可以看做消息的编号,它是一个64位的长整形值。

此时运行消费者客户端,发现消息消费成功,并且在队列中被移除:

在这里插入图片描述

在这里插入图片描述

点关注,不迷路,这是一个程序员都想要关注的二维码

在这里插入图片描述

<think>好的,用户现在问的是如何保证RabbitMQ消息的可靠传输。之前他们问过如何避免重复消费,而这次的问题更侧重于消息传输的可靠性。我需要回忆一下之前回答的结构,确保这次回答同样详细且有结构。 首先,我需要确定用户可能的使用场景。他们可能在开发一个分布式系统,使用RabbitMQ作为消息中间件。可靠传输是确保消息从生产者到消费者都不丢失的关键,特别是在金融、电商等对数据一致性要求高的领域。用户可能是开发者或架构师,需要确保系统的高可靠性。 接下来,用户的需求是什么?他们想知道如何配置RabbitMQ来防止消息丢失,可能涉及生产者的确认机制、队列持久化、消费者的手动确认等。用户可能已经遇到消息丢失的问题,或者正在设计系统,希望提前规避风险。 深层需求方面,用户可能希望了解全面的解决方案,包括生产端、Broker端和消费端的策略,以及监控和灾备措施。他们可能还需要实际配置示例,比如代码片段或配置参数,以便直接应用。 回想之前的回答结构,用户可能习惯分点说明,每个部分有原理和实现方式。我需要保持类似的格式,确保清晰易读。同时,可能需要对比不同确认模式(如事务、Confirm模式)的优缺点,帮助用户选择适合的方案。 需要注意RabbitMQ的特性,比如持久化需要同时设置队列消息的属性,手动ACK的重要性,以及如何处理网络故障或服务器崩溃的情况。此外,高可用性方面,镜像队列和集群部署可能也是用户关心的点。 在组织内容时,可以分三个主要部分:生产者、Broker、消费者,每个部分详细说明如何确保可靠传输。最后补充高可用和监控措施,确保全面覆盖。 可能用户对某些术语不熟悉,比如Publisher Confirm,需要简要解释。同时,给出配置示例或代码片段会更有帮助,比如Java或Spring Boot的配置,但用户可能使用不同的语言,所以保持示例通用或注明语言。 还要注意之前回答中的注意事项部分,比如性能权衡,确保用户了解可靠性和性能之间的平衡,比如持久化会增加开销,手动ACK可能影响吞吐量,但这是必要的代价。 最后,确保回答结构清晰,重点突出,使用用户容易理解的语言,避免过于技术化但又不失专业性。可能还需要提及一些常见错误,比如忘记设置持久化或错误处理ACK,导致消息丢失的情况。</think>在 RabbitMQ保证消息的可靠传输,需要从 **生产者、Broker、消费者** 三个环节协同设计,以下是核心方案: --- ### **一、生产者端:确保消息成功投递** #### 1. **事务机制(不推荐高性能场景)** ```java channel.txSelect(); // 开启事务 try { channel.basicPublish(exchange, routingKey, props, body); channel.txCommit(); // 提交事务 } catch (Exception e) { channel.txRollback(); // 回滚 } ``` - **缺点**:同步阻塞,吞吐量下降约 2~10 倍。 #### 2. **Publisher Confirm 模式(推荐)** - **异步确认**:生产者发送消息后,Broker 异步返回确认信号(ACK/NACK)。 - **实现方式**: ```java channel.confirmSelect(); // 开启 Confirm 模式 channel.addConfirmListener((sequenceNumber, multiple) -> { // ACK 回调 // 消息成功持久化到磁盘(若配置) }, (sequenceNumber, multiple) -> { // NACK 回调 // 消息投递失败,需重试或记录 }); ``` - **优势**:非阻塞,支持批量确认(`multiple=true`)。 --- ### **二、Broker 端:防止消息丢失** #### 1. **队列持久化** ```java // 声明队列时设置 durable=true channel.queueDeclare("order_queue", true, false, false, null); ``` - **要求**:队列元数据(名称、属性)持久化到磁盘。 #### 2. **消息持久化** ```java AMQP.BasicProperties props = new AMQP.BasicProperties.Builder() .deliveryMode(2) // 1=非持久,2=持久 .build(); channel.basicPublish(exchange, routingKey, props, body); ``` - **注意**:需同时设置队列消息为持久化,否则重启后消息仍可能丢失。 #### 3. **镜像队列(高可用)** ```shell # 配置策略,镜像到所有节点 rabbitmqctl set_policy ha-all "^ha\." '{"ha-mode":"all"}' ``` - **作用**:消息在集群多个节点间复制,避免单点故障。 --- ### **三、消费者端:确保消息正确处理** #### 1. **手动 ACK 确认** ```java channel.basicConsume(queue, false, (consumerTag, delivery) -> { // autoAck=false try { processMessage(delivery.getBody()); channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false); // 手动ACK } catch (Exception e) { // 处理失败:NACK+重入队列 或 记录死信 channel.basicNack(deliveryTag, false, true); } }, consumerTag -> {}); ``` - **关键点**: - 关闭 `autoAck`(避免消息未处理完即丢失)。 - ACK 应在业务逻辑成功**后**发送。 - 失败时选择 `basicNack(requeue=true)` 或转入死信队列。 #### 2. **消费端重试机制** - **本地重试**:捕获异常后重试(需限制最大次数)。 - **死信队列(DLX)**:超过重试次数后转发到 DLX,人工干预处理。 ```java Map<String, Object> args = new HashMap<>(); args.put("x-dead-letter-exchange", "dlx.exchange"); channel.queueDeclare("order_queue", true, false, false, args); ``` --- ### **四、补充保障措施** #### 1. **网络断连自动恢复** ```java ConnectionFactory factory = new ConnectionFactory(); factory.setAutomaticRecoveryEnabled(true); // 自动重连 factory.setNetworkRecoveryInterval(5000); // 重试间隔 ``` #### 2. **消息落库 + 状态追踪** - 生产者在发送前将消息写入本地数据库,标记状态为「发送中」。 - 通过定时任务补偿未确认的消息(类似 MQTT 的 QoS 2 机制)。 #### 3. **监控报警** - 监控指标:未确认消息数、队列堆积量、节点健康状态。 - 工具:Prometheus + Grafana、RabbitMQ Management Plugin。 --- ### **可靠性保障全链路总结** | 环节 | 技术方案 | 目标 | |----------|-----------------------------------|--------------------------| | 生产者 | Confirm 模式 + 消息持久化 | 确保消息到达 Broker | | Broker | 队列持久化 + 镜像队列 | 防止消息丢失和单点故障 | | 消费者 | 手动 ACK + 死信队列 + 重试 | 保证消息最终被消费 | | 兜底措施 | 本地消息表 + 监控报警 | 业务层补偿和快速故障发现 | --- ### **注意事项** 1. **性能权衡**:持久化和 Confirm 模式会增加延迟,需根据业务容忍度调整。 2. **幂等性**:重试机制可能导致重复消费,需结合[消息去重方案](链接)实现。 3. **脑裂问题**:镜像队列在集群网络分区时可能产生数据不一致,需配置 `pause_minority` 策略。 通过以上多级保障,可达到 **99.99%+** 的消息可靠性(实际取决于硬件和网络稳定性)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值