【RabbitMQ】RabbitMQ 消息的可靠性 —— 生产者和消费者消息的确认,消息的持久化以及消费失败的重试机制_rabbitmq 生产者消息确认

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新大数据全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip204888 (备注大数据)
img

正文

首先让我们来了解一下,在消息队列中,消息从生产者发送到交换机,再到队列,最后到消费者,有哪些情况会导致消息的丢失?

  • 发送时丢失:

    • 生产者发送的消息未送达交换机;
    • 消息到达交换机后未到达队列;
  • MQ 宕机,队列中的消息会丢失;

  • 消费者接收到消息后未消费就宕机了。

确保消息队列的可靠性是分布式系统中不可或缺的一部分,因此我们需要采取措施来应对这些挑战。为了解决上述消息可靠性问题,RabbitMQ提供了一系列的机制和最佳实践,以确保消息在整个传递过程中得到妥善处理和保护。

本文将深入探讨如何应对这些挑战,介绍消息队列中的关键概念,并详细讨论 RabbitMQ 提供的解决方案,包括生产者消息的确认、消息的持久化、消费者消息的确认以及消息消费失败的重试机制。这些措施将有助于确保消息队列在应用程序中的可靠性和稳定性。

一、生产者消息的确认

1.1 生产者确认机制

RabbitMQ 提供了 publisher confirm 机制,这是一种用于解决消息发送过程中可能出现的丢失问题的机制。当消息发送到 RabbitMQ 后,系统会返回一个结果给消息的发送者,以指示消息的处理状态。这个结果有两种可能的值:

  • publisher-confirm,发送者确认:

    • 消息成功投递到交换机,系统返回 ack(确认)。
    • 消息未能成功投递到交换机,系统返回 nack(未确认)。
  • publisher-return,发送者回执:

    • 消息成功投递到交换机,但是没有成功路由到队列,系统返回 ACK,同时提供路由失败的原因。

这个确认机制的目的是确保消息在发送到消息队列后,发送者能够获得有关消息处理状态的明确反馈,从而可以采取适当的措施,例如重发消息或记录失败信息。

需要注意的是,为了实现这一机制,需要为每条消息设置一个全局唯一的标识符,以便区分不同的消息,避免在确认过程中出现冲突。

例如下图所示:
示例图

确保消息生产者能够获得有关消息状态的反馈是确保消息可靠性的关键一步,因为它有助于解决消息可能在发送期间丢失的问题。这是构建可靠的消息队列系统中的重要组成部分。

1.2 实现生产者消息的确认

下面将通过一个 Java 的 Spring Boot 项目来演示如何实现生产者消息的确认。这个项目的结构如下:


这个项目有两个模块,其中 consumer 负责对消息的消费,而 publisher 负责发送消息。下面是在 publisher 模块中实现消息确认的具体步骤:

  1. publisher 服务中的 application.yml 文件中添加如下配置:
spring:
  rabbitmq:
    publisher-confirm-type: correlated
    publisher-returns: true
    template:
      mandatory: true

对这个配置的详细说明:

  • publish-confirm-type:开启 publisher-confirm 功能,这里支持两种类型:
    • simple:同步等待 confirm 结果,直到超时;
    • correlated:异步回调,定义ConfirmCallback,MQ返回结果时会回调这个 ConfirmCallback
  • publish-returns:开启 publish-return 功能,同样是基于 callback 机制,不过是定义 ReturnCallback
  • template.mandatory:定义消息路由失败时的策略。true,则调用 ReturnCallbackfalse,则直接丢弃消息。
  1. RabbitTemplate 配置 ReturnCallback
@Configuration
@Slf4j
public class CommonConfig implements ApplicationContextAware {

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        // 获取 RabbitTemplate 对象
        RabbitTemplate rabbitTemplate = applicationContext.getBean(RabbitTemplate.class);
        // 配置 ReturnCallBack
        rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
            // 记录日志
            log.error("消息发送到队列失败,响应码:{},失败原因:{},交换机:{},路由 Key:{},消息:{}",
                    replyCode, replyText, exchange, routingKey, message.toString());
            // 如果有需要,接下来可以重发消息,或者执行其他通知逻辑
        });
    }
}

由于每个 RabbitTemplate 只能配置一个 ReturnCallback,并且 RabbitTemplate 在Spring 中是一个全局对象,因此需要在项目启动过程中配置。

上述代码就是一个 Spring Boot 的配置类,通常用于在项目启动时配置一些全局的设置。在这个配置类中,实现了 ApplicationContextAware 接口,用于获取 Spring 应用上下文(ApplicationContext)对象。主要作用是配置 RabbitMQ 的 ReturnCallback,以处理消息发送到队列失败的情况。

  1. 发送消息,指定消息的 ID以及消息的 ConfirmCallback
@Test
public void testSendMessage2SimpleQueue() throws InterruptedException {
    String routingKey = "simple.test";
    // 1. 准备消息
    String message = "hello, spring amqp!";
    // 2. 准备 CorrelationDate
    // 2.1.消息ID
    CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
    // 2.2.准备 ConfirmCallback
    correlationData.getFuture().addCallback(confirm -> {
        // 消息发送成功
        // 判断结果
        if(confirm != null && confirm.isAck()){
            // ACK
            log.debug("消息投递到交换机成功!消息 ID: {}", correlationData.getId());
        } else {
            // NACK
            log.error("消息投递到交换机失败!消息 ID: {}", correlationData.getId());
        }
    }, throwable -> {
        // 发送失败
        // 记录日志
        log.error("消息发送失败!", throwable);
        // 重发消息...
    });
    // 3. 发送消息
    rabbitTemplate.convertAndSend("amq.topic", routingKey, message, correlationData);
}

这是一个 Java 测试方法,用于发送消息到 RabbitMQ 队列,并指定消息的 ID 以及 ConfirmCallback(确认回调)。以下是对这段代码的详细解释:

  1. testSendMessage2SimpleQueue: 这是一个测试方法,用于演示如何发送消息到名为 “simple.test” 的 RabbitMQ 队列。

  2. String routingKey = "simple.test";: 定义了消息的路由键,这是用于将消息路由到特定队列的关键。

  3. 准备消息:将要发送的消息内容存储在 message 变量中。

  4. 准备 CorrelationData

    • CorrelationData 用于关联消息的 ID。
    • 使用 UUID.randomUUID().toString() 生成一个全局唯一的消息 ID。
  5. 准备 ConfirmCallback

    • CorrelationData.getFuture().addCallback(confirm -> { ... }, throwable -> { ... }) 定义了 ConfirmCallback,该回调会在消息的发送状态发生变化时触发。
    • ConfirmCallback 中,判断了消息是否成功投递到交换机:
      • 如果 confirm 不为 null 且 confirm.isAck()true,则表示消息成功到达交换机,记录一条成功的日志。
      • 否则,如果消息未成功到达交换机,则记录一条失败的日志。
    • throwable 回调中,处理了发送失败的情况,记录了失败的日志,可以在这里添加重发消息或其他失败处理逻辑。
  6. 发送消息:

    • 使用 rabbitTemplate.convertAndSend("amq.topic", routingKey, message, correlationData); 发送消息到 RabbitMQ。
    • 参数包括交换机名称、路由键、消息内容和关联的 CorrelationData

这段代码演示了如何发送消息并在消息状态变化时使用 ConfirmCallback 处理消息的确认情况。通过关联消息 ID 和 ConfirmCallback,可以确保消息的可靠性,根据确认情况采取适当的措施。

1.3 验证生产者消息的确认

下面通过可以运行上述测试代码来查看生产者的消息确认情况:

  1. 正常发送消息

直接执行测试方法,可以发现消息成功投递到交换机:

  1. 发送消息失败

此时,将交换机的名称改成一个错误不存在的:
然后再次执行测试方法:

发现此时消息投递到交换机失败,说明此时返回的是 NACK,并且提示了错误的原因是找不到名为 aamq.topic的交换机。

  1. 成功发送消息,但是路由失败

此时将交换机的名称修改回来,但是将路由 Key 修改成错误的:


然后执行测试方法:

通过输出的日志可以发现,消息成功投递到了交换机,但是由于路由 Key 不正确,导致路由不到 simple,queue,从而触发调用了上文配置的ReturnCallback

二、消息的持久化

在通过上文的生产者消息确认机制之后,确保了消息能够正确的发送到队列中,但是这并不意味着消息就安全了。因为 RabbitMQ 默认是内存储存的,如果出现了 RabbitMQ 宕机的情况,那么此时队列中的消息还是会丢失。要确保消息能够真正的安全,我们还需要实现消息的持久化。

2.1 演示消息的丢失

例如,现在 simple.queue 中存在 3 条消息:

这些消息是通过 RabbitMQ 自带的交换机 amp.topic 进行转发的:

然后我们重启一下 RabbitMQ 服务,看一看队列中的消息是否还存在:


此时我们重新服务 RabbitMQ 的控制台,发现连 simple.queue 都消失了:

但是RabbitMQ自带的 amp.topic 交换机还存在:

说明,这个交换机是持久化储存的,如果仔细观察可以发现,这些所有的交换机的 Features 都带有一个 D ,即持久化 Durable。

因此要让我们自己创建的队列或者交换机也能持久存在,就可以否选上 Durable 这个选项:

2.2 声明持久化的交换机和队列

通过上文我们知道了可以在 RabbitMQ 的控制台创建交换机和队列的时候可以勾选 Durable 来达到持久化的目的,但是如果使用代码来创建持久化的交换机和队列呢?下面我将使用 Java 代码来演示这个过程:

由于消费者comsumer在启动的时候可以帮我们创建交换机和队列,因此将交换机和队列的声明交给 consumer 来完成。

  1. 声明持久化的交换机
@Configuration
public class CommonConfig {
    @Bean
    public DirectExchange simpleDirect(){
        // DirectExchange的构造方法有三个参数:交换机名称、是否持久化、当没有 queue 与其绑定时是否自动删除
        return new DirectExchange("simple.direct", true, false);
    }
}

  1. 声明持久化的队列持久化
@Configuration
public class CommonConfig {

		// ...
		
    @Bean
    public Queue simpleQueue(){
        // 使用QueueBuilder构建队列,其中使用 durable 方法就是持久化的
        return QueueBuilder.durable("simple.queue").build();
    }
}

当完成了上面两步之后,我们可以启动 consumer 服务:

在这里插入图片描述
此时,我们发现成功创建了simple.direct交换机和 simple.queue 队列,并且它们都是持久的。然后停止consumer 服务,在 RabbitMQ 的控制台中向 simple.queue 添加一条消息:


然后再次重启 RabbitMQ 服务,发现刚才创建的交换机和队列都还在,但是消息却没有了:
因为我刚才添加的是非持久化的消息:

2.3 发送持久化的消息

同样,在控制台添加消息的时候可以设置消息的持久化和非持久化,下面让我来演示然后在使用 Java 代码发送持久化的消息:

@Test
public void testDurableMessage() {
    // 1. 准备消息
    Message message = MessageBuilder.withBody("hello, simple.queue".getBytes(StandardCharsets.UTF\_8))
            .setDeliveryMode(MessageDeliveryMode.PERSISTENT)
            .build();
    // 2. 发送消息
    rabbitTemplate.convertAndSend("simple.queue", message);
}

在发送持久化的消息需要使用MessageBuilder来构建消息,其中withBody用于指定消息体;setDeliveryMode用来设置消息的发送类型,可以是持久化的,也可以是非持久化的;build 与构建消息。

完成上述代码之后,我们可以执行这个测试方法:

查看 RabbitMQ 的控制台,发现成功发送了消息,并且其中的 delivery_mode 为 2,代表的就是持久化:

再次重启 RabbitMQ 服务:

此时发现刚才的消息并没有丢失,至此我们就完成了持久化消息的发送,进一步确保了消息的可靠性。另外,其实在使用 Spring AMQP 创建的交换机,队列和发送的消息都是持久化的。

三、消费者消息的确认

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注大数据)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

此时发现刚才的消息并没有丢失,至此我们就完成了持久化消息的发送,进一步确保了消息的可靠性。另外,其实在使用 Spring AMQP 创建的交换机,队列和发送的消息都是持久化的。

三、消费者消息的确认

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注大数据)
[外链图片转存中…(img-DayOzvXk-1713350021288)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

<think>好的,我现在需要帮用户解决关于RabbitMQ如何实现消息可靠传输的问题,具体包括生产者确认持久化配置消费者手动确认,并且需要提供Java代码示例。首先,我得回忆一下RabbitMQ的相关知识,确保自己理解正确。 用户提到了生产者确认机制。我记得生产者确认分为事务机制确认机制两种,但确认机制性能更好。需要确认生产者发送消息后,如何确保消息正确到达Broker。可能需要配置ConnectionFactory开启确认模式,然后通过RabbitTemplate设置确认回调。同时,要处理成功失败的情况,比如记录日志或者重发。 然后是持久化配置。消息持久化需要设置消息的属性为PERSISTENT,同时队列交换器也要设置为持久化的。这需要用户在声明队列交换器的时候,将durable参数设为true。这样即使RabbitMQ重启,消息也不会丢失。但要注意的是,仅仅设置消息持久化是不够的,如果队列不持久化,重启后队列不存在,消息还是会丢失。 接下来是消费者手动确认消费者需要关闭自动确认,改为手动确认。这样在处理完消息后,手动发送ack,或者在处理失败时发送nack,让消息重新入队或者进入死信队列。这里可能需要设置监听容器的acknowledgeMode为MANUAL,然后在消费者代码里调用channel.basicAck或basicNack。 现在要结合这三个部分,确保整个流程可靠。生产者确认确保消息到达Broker,持久化确保消息在Broker中保存到磁盘,消费者手动确认确保消息被正确处理后才从队列移除。这样整个链路就可靠了。 接下来是Java代码示例。需要展示如何配置生产者确认,比如配置ConnectionFactory的publisherConfirmType为CONFIRMATORY。然后设置RabbitTemplate的确认回调,处理acknack的情况。对于持久化,声明队列交换器时设置durable为true,发送消息时设置deliveryMode为PERSISTENT。消费者部分,配置监听容器的acknowledgeMode为MANUAL,并在监听器里手动确认。 还需要考虑可能的异常处理,比如生产者发送失败后的重试机制消费者处理失败时的重试或记录错误。同时,引用用户提供的参考内容,比如手动ACKQoS的配置,可能需要在消费者端设置prefetchCount,避免消息堆积。 检查用户提供的引用,引用[1]提到手动ACKQoS,引用[2]说明RabbitMQ对未确认消息的处理,引用[3][4]是端口号RabbitTemplate的初始化。需要确保代码示例中的端口正确,并正确使用RabbitTemplate。 最后,要生成相关问题,帮助用户深入理解。比如如何配置QoS,如何处理消息重复消费,如何实现重试机制等。确保问题覆盖用户可能感兴趣的扩展内容。</think>### RabbitMQ 消息可靠传输实现方案 通过生产者确认机制持久化配置消费者手动确认三部分实现完整消息可靠性保障: #### 一、生产者确认配置 ```java @Configuration public class RabbitConfig { @Bean public ConnectionFactory connectionFactory() { CachingConnectionFactory factory = new CachingConnectionFactory(); factory.setHost("localhost"); factory.setPort(5672); // 引用[3]端口配置 factory.setPublisherConfirmType(CachingConnectionFactory.ConfirmType.CORRELATED); // 开启确认模式 return factory; } @Bean public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) { RabbitTemplate template = new RabbitTemplate(connectionFactory); // 引用[4]初始化方式 template.setMandatory(true); // 开启强制回退 // 确认回调配置 template.setConfirmCallback((correlationData, ack, cause) -> { if (ack) { System.out.println("消息到达交换机:" + correlationData.getId()); } else { System.out.println("消息未到达交换机:" + cause); } }); // 回退回调配置 template.setReturnsCallback(returned -> { System.out.println("消息无法路由到队列:" + returned.getMessage().getMessageProperties().getReceivedRoutingKey()); }); return template; } } ``` #### 二、持久化配置 ```java @Bean public Queue durableQueue() { return new Queue("reliable.queue", true); // 持久化队列 } @Bean public DirectExchange durableExchange() { return new DirectExchange("reliable.exchange", true, false); // 持久化交换机 } // 发送消息时设置持久化 public void sendDurableMessage() { Message message = MessageBuilder .withBody("持久化消息".getBytes()) .setDeliveryMode(MessageDeliveryMode.PERSISTENT) // 消息持久化 .build(); rabbitTemplate.send("reliable.exchange", "reliable.key", message); } ``` #### 三、消费者手动ACK ```java @RabbitListener(queues = "reliable.queue") public void handleMessage(Message message, Channel channel) throws IOException { try { String msg = new String(message.getBody()); System.out.println("收到消息:" + msg); // 业务处理逻辑... channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); // 手动确认 } catch (Exception e) { // 处理失败时拒绝消息(可配置重试机制) channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true); } } // 配置监听容器 @Bean public SimpleRabbitListenerContainerFactory containerFactory(ConnectionFactory connectionFactory) { SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory(); factory.setConnectionFactory(connectionFactory); factory.setAcknowledgeMode(AcknowledgeMode.MANUAL); // 手动确认模式 factory.setPrefetchCount(10); // QoS预取数量配置,引用[1] return factory; } ``` #### 实现要点说明: 1. **生产者确认**: - 使用`ConfirmCallback`确认消息是否到达交换机 - 使用`ReturnsCallback`处理无法路由的消息 - 需要配置`publisher-confirm-type``publisher-returns` 2. **持久化保障**: - 队列/交换机声明时设置`durable=true` - 消息设置`PERSISTENT`投递模式 - 确保消息在服务端异常时不会丢失[^2] 3. **手动确认机制**: - 关闭自动确认(`AcknowledgeMode.MANUAL`) - 正确处理成功后`basicAck`失败后`basicNack` - 结合QoS预取数量控制处理节奏[^1]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值