rabbitMq生产者角度:消息持久化、事务机制、PublisherConfirm、mandatory

本文介绍了如何在RabbitMQ中实现消息持久化、事务处理及消息确认,包括设置交换机、队列和消息的持久化,使用事务机制确保消息发送成功,以及通过PublisherConfirm机制提高性能并确保消息不丢失。

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

在交换机简介一篇中,通过生产者和消费者都声明同样的交换机同样的队列,可以保证无论生产者还是消费者先运行起来,都不会由于交换机或者队列不存在而出现错误。但是上一篇所有的代码都存在如下两个问题:
1、服务器宕机,消息将丢失。
2、发送的消息如果没有正确路由到队列,例如生产者先运行消费者未运行的情况,那么消息将被丢弃,且生产者没有收到任何反馈。
下面的介绍将会解决这些问题。

一、消息持久化

以前我的消息都是临时消息,服务器宕机重启后,没有被处理的消息将会被丢弃掉,通过设置交换机持久化、队列持久化、消息持久化可以解决这一个问题。
生产者:

        //声明持久化交换机,类型为direct
        boolean durable=true;
        channel.exchangeDeclare(EXCHANGE_NAME, "direct", durable, false, null);
        String message="hello world";
        //发送消息,并设置为持久化
        channel.basicPublish(EXCHANGE_NAME, "blue", MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes()); 

消费者:

        //声明持久化交换机
        boolean durable=true;
        channel.exchangeDeclare(EXCHANGE_NAME, "direct", durable, false, null);
        //声明持久化队列
        channel.queueDeclare(queueName, durable, false, false, null); 
        channel.queueBind(queueName, EXCHANGE_NAME, "blue");  
        System.out.println(" [*] Waiting for messages. To exit press CTRL+C");  
        QueueingConsumer consumer = new QueueingConsumer(channel);
        channel.basicConsume(queueName, true, consumer);  
        while (true)  
        {  
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();  
            String message = new String(delivery.getBody());  
            String routingKey = delivery.getEnvelope().getRoutingKey();  
            System.out.println(" [x] Received routingKey = " + routingKey   + ",msg = " + message + ".");  
        }  

即使交换机和队列都是持久化的,消息是否持久化取决于发送方发送消息时指定的properties,MessageProperties类中有一些常用的Properties对象,例如持久化消息MessageProperties.PERSISTENT_TEXT_PLAIN。

二、事务和消息确认

消息持久化,可以保证已经发送到队列中的消息被持久化到磁盘上,服务器重启后可以从磁盘恢复这些消息。那么对于正在发送还没有成功的消息呢?两种解决方案:

1、事务

RabbitMQ中与事务机制有关的方法有三个,分别是Channel里面的txSelect(),txCommit()以及txRollback(),txSelect用于将当前Channel设置成是transaction模式,txCommit用于提交事务,txRollback用于回滚事务,在通过txSelect开启事务之后,我们便可以发布消息给broker代理服务器了,如果txCommit提交成功了,则消息一定是到达broker了,如果在txCommit执行之前broker异常奔溃或者由于其他原因抛出异常,这个时候我们便可以捕获异常通过txRollback回滚事务了。

channel.txSelect();
        try{
            channel.basicPublish(EXCHANGE_NAME, "blue", MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes()); 
            System.out.println("commit");
            channel.txCommit();
        }catch(Exception e){
            System.out.println("rollback");
            channel.txRollback();
        }
2、PublisherConfirm

Publisher Confirm机制(又称为Confirms或Publisher Acknowledgements)是作为解决事务机制性能开销大(导致吞吐量下降)而提出的另外一种保证消息不会丢失的方式。

生产者将信道设置成confirm模式,一旦信道进入confirm模式,所有在该信道上面发布的消息都会被指派一个唯一的ID(从1开始),一旦消息被投递到所有匹配的队列之后,broker就会发送一个确认给生产者(包含消息的唯一ID),这就使得生产者知道消息已经正确到达目的队列了,如果消息和队列是可持久化的,那么确认消息会将消息写入磁盘之后发出,broker回传给生产者的确认消息中deliver-tag域包含了确认消息的序列号,此外broker也可以设置basic.ack的multiple域,表示到这个序列号之前的所有消息都已经得到了处理。

confirm模式最大的好处在于他是异步的,一旦发布一条消息,生产者应用程序就可以在等信道返回确认的同时继续发送下一条消息,当消息最终得到确认之后,生产者应用便可以通过回调方法来处理该确认消息,如果RabbitMQ因为自身内部错误导致消息丢失,就会发送一条nack消息,生产者应用程序同样可以在回调方法中处理该nack消息。

在channel 被设置成 confirm 模式之后,所有被 publish 的后续消息都将被 confirm(即 ack) 或者被nack一次。但是没有对消息被 confirm 的快慢做任何保证,并且同一条消息不会既被 confirm又被nack 。

Confirm机制在性能上要比事务优越很多。但是Confirm机制,无法进行回滚,就是一旦服务器崩溃,生产者无法得到Confirm信息,生产者其实本身也不知道该消息吃否已经被持久化,只有继续重发来保证消息不丢失,但是如果原先已经持久化的消息,并不会被回滚,这样队列中就会存在两条相同的消息,系统需要支持去重。

Channel对象提供的ConfirmListener()回调方法只包含deliveryTag(当前Chanel发出的消息序号),我们需要自己为每一个Channel维护一个unconfirm的消息序号集合,每publish一条数据,集合中元素加1,每回调一次handleAck方法,unconfirm集合删掉相应的一条(multiple=false)或多条(multiple=true)记录。从程序运行效率上看,这个unconfirm集合最好采用有序集合SortedSet存储结构。

SortedSet<Long> confirmSet = Collections.synchronizedSortedSet(new TreeSet<Long>());
 channel.confirmSelect();
        channel.addConfirmListener(new ConfirmListener() {
            public void handleAck(long deliveryTag, boolean multiple) throws IOException {
                if (multiple) {
                    confirmSet.headSet(deliveryTag + 1).clear();
                } else {
                    confirmSet.remove(deliveryTag);
                }
            }
            public void handleNack(long deliveryTag, boolean multiple) throws IOException {
                System.out.println("Nack, SeqNo: " + deliveryTag + ", multiple: " + multiple);
                if (multiple) {
                    confirmSet.headSet(deliveryTag + 1).clear();
                } else {
                    confirmSet.remove(deliveryTag);
                }
            }
        });

        while (true) {
            long nextSeqNo = channel.getNextPublishSeqNo();
            channel.basicPublish(ConfirmConfig.exchangeName, ConfirmConfig.routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN, ConfirmConfig.msg_10B.getBytes());
            confirmSet.add(nextSeqNo);
        }

三、mandatory

事务机制和消息确认机制都是为了保证异常状态下的消息不丢失,其实正常状态下也可能存在消息丢失问题,例如交换机按照路由规则未找到该消息对应的队列。confirm机制配合mandatory标志使用可以实现消息发送的可靠性,且性能较好。
上一篇博客中,曾经介绍过生产者发送消息时可以设置的一个标志位mandatory。
当mandatory标志位设置为true时,如果exchange根据自身类型和消息routeKey无法找到一个符合条件的queue,那么会调用basic.return方法将消息返还给生产者, channel.addReturnListener添加一个监听器,当broker执行basic.return方法时,会回调handleReturn方法,这样我们就可以处理变为死信的消息了;当mandatory设为false时,出现上述情形broker会直接将消息扔掉;通俗的讲,mandatory标志告诉broker代理服务器至少将消息route到一个队列中,否则就将消息return给发送者。
关键代码:

    //声明持久化交换机,类型为direct
        boolean durable=true;
        channel.exchangeDeclare(EXCHANGE_NAME, "direct", durable, false, null);
        //添加回调监听,用于处理basic.return命令返回的消息
        channel.addReturnListener(new ReturnListener() {

            public void handleReturn(int replyCode,String replyText,String exchange,String routingKey,AMQP.BasicProperties properties,byte[] body)
                throws IOException{
                System.out.println("handleReturn");
                System.out.println("replyCode:"+replyCode);
                System.out.println("replyText:"+replyText);
                System.out.println("messagebody:"+new String(body));
            }
        });

        String message="hello world";
        //设置mandatory为true
        boolean mandatory=true;
        channel.basicPublish(EXCHANGE_NAME, "blue", mandatory, false, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes());

运行结果:
[x] Sent ‘hello world’
handleReturn
replyCode:312
replyText:NO_ROUTE
messagebody:hello world

<think>我们正在讨论RabbitMQ生产者消息确认机制。根据提供的引用,我们可以总结出RabbitMQ生产者消息确认机制包括两种:Publisher Confirm(发布者确认)和Publisher Return(发布者返回)。下面我将详细解释这两种机制。 ### 1. **Publisher Confirm(发布者确认)** - **机制原理**:当生产者发送消息RabbitMQ的Exchange后,RabbitMQ会向生产者发送一个确认(acknowledgement,简称ack)表示消息已经成功接收。如果消息未能被处理(例如,Exchange无法路由消息),RabbitMQ会发送一个否定确认(negative acknowledgement,简称nack)。 - **使用场景**:确保消息成功到达Exchange。 - **确认方式**: - 同步确认:生产者发送消息后阻塞等待确认(性能较差,不常用)。 - 异步确认:生产者通过回调函数处理确认消息(推荐方式,高性能)。 - **配置方法**(基于Spring Boot): ```yaml spring: rabbitmq: publisher-confirm-type: correlated # 开启异步确认模式 ``` - **代码示例**(异步确认): ```java // 设置确认回调 rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> { if (ack) { // 消息成功到达Exchange } else { // 消息未到达Exchange,需要处理失败(如重发) } }); ``` ### 2. **Publisher Return(发布者返回)** - **机制原理**:当消息成功到达Exchange,但无法路由到任何队列时,RabbitMQ会通过Return机制消息返回给生产者(注意:只有设置了`mandatory=true`或`mandatory-expression`为`true`时才会返回)。 - **使用场景**:确保消息能够被正确路由到队列。 - **配置方法**(基于Spring Boot): ```yaml spring: rabbitmq: publisher-returns: true # 开启Return机制 template: mandatory: true # 设置mandatory为true ``` - **代码示例**(设置Return回调): ```java rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> { // 处理无法路由的消息 }); ``` ### 两种机制的工作流程 1. **消息发送过程**: - 生产者发送消息到Exchange。 - 如果消息成功到达Exchange,则触发`ConfirmCallback`(ack=true)。 - 如果消息未到达Exchange(例如,网络问题导致连接断开),则触发`ConfirmCallback`(ack=false)。 - 如果消息到达Exchange但无法路由到队列(例如,没有匹配的绑定),且设置了`mandatory=true`,则触发`ReturnCallback`。 2. **注意事项**: - 确认机制只保证消息到达Exchange,不保证消息被消费者处理。 - 要确保消息被消费者处理,需要使用消费者确认机制(Consumer Acknowledgement)。 ### 代码示例(结合两种机制) ```java @Configuration public class RabbitMQConfig { @Bean public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) { RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory); // 设置确认回调 rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> { if (ack) { // 消息成功到达Exchange } else { // 消息未到达Exchange,记录日志或重发 } }); // 设置返回回调 rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> { // 消息无法路由到队列,记录日志或处理 }); return rabbitTemplate; } } ``` ### 总结 - **Publisher Confirm**:解决消息是否成功到达Exchange的问题。 - **Publisher Return**:解决消息到达Exchange后无法路由到队列的问题。 - **两者结合**:可以确保消息生产者RabbitMQ的可靠性传递。 ### 相关问题 §§相关问题§§ 1. RabbitMQ生产者消息确认机制和消费者确认机制有什么区别? 2. 如何配置RabbitMQ消息无法路由时进行重试? 3. RabbitMQ生产者确认机制在高并发场景下如何优化性能? 4. 如果RabbitMQ集群节点故障,生产者确认机制如何保证消息不丢失? 5. 如何监控RabbitMQ生产者消息确认的成功率和失败原因?</think>### RabbitMQ 生产者消息确认机制详解 RabbitMQ 提供了两种生产者消息确认机制,确保消息可靠投递: #### 1. **Publisher Confirm(发布者确认)** **核心机制**: 生产者发送消息后,RabbitMQ 会异步返回确认回执(ack/nack)。 - ✅ **ack**:消息已成功写入磁盘(持久化消息)或内存(非持久化消息) - ❌ **nack**:消息处理失败(通常因节点故障或队列满导致) **工作流程**: ```mermaid sequenceDiagram 生产者->>+RabbitMQ: 发送消息(带唯一ID) RabbitMQ-->>-生产者: 返回ack(成功) or RabbitMQ-->>-生产者: 返回nack(失败) ``` **配置方式**(Spring Boot): ```yaml spring: rabbitmq: publisher-confirm-type: correlated # 开启异步确认 ``` **代码实现**: ```java // 配置确认回调 rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> { if (ack) { log.info("消息成功投递, ID: {}", correlationData.getId()); } else { log.error("消息投递失败, ID: {}, 原因: {}", correlationData.getId(), cause); // 重试逻辑 } }); // 发送消息 rabbitTemplate.convertAndSend( "exchange", "routingKey", message, new CorrelationData("msg-123") // 唯一ID ); ``` --- #### 2. **Publisher Return(发布者返回)** **核心机制**: 当消息无法路由到队列时(如路由键错误),RabbitMQ消息返回给生产者。 **触发条件**: - 消息设置了 `mandatory=true` - 交换机无法将消息路由到任何队列 **配置方式**: ```yaml spring: rabbitmq: publisher-returns: true # 开启返回机制 template: mandatory: true # 强制消息路由检查 ``` **代码实现**: ```java rabbitTemplate.setReturnsCallback(returned -> { log.error("消息路由失败: {}", "交换机=" + returned.getExchange() + ", 路由键=" + returned.getRoutingKey() + ", 错误码=" + returned.getReplyCode() ); }); ``` --- ### 两种机制对比 | 特性 | Publisher Confirm | Publisher Return | |--------------------|---------------------------|---------------------------| | **触发场景** | 消息到达Broker时 | 消息无法路由到队列时 | | **确认类型** | ack/nack | 返回消息体 | | **网络要求** | 需要保持TCP连接 | 需要保持TCP连接 | | **典型用途** | 保证消息持久化成功 | 处理路由错误 | | **性能影响** | 低(异步回调) | 低(异步回调) | --- ### 最佳实践 1. **组合使用**:同时开启两种机制实现端到端可靠性 2. **幂等处理**:消息ID + 重试机制应对网络抖动 3. **超时控制**:设置等待确认超时时间(默认无限等待) 4. **异步回调**:避免阻塞生产者线程 ```java // 完整配置示例 @Bean public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) { RabbitTemplate template = new RabbitTemplate(connectionFactory); template.setMandatory(true); template.setConfirmCallback(...); template.setReturnsCallback(...); template.setReplyTimeout(5000); // 5秒超时 return template; } ``` > 💡 **关键点**:确认机制仅保证消息到达Broker,消费者确认需配合 `consumer acknowledgement` 实现完整可靠性[^1][^2][^3]。 --- ### 相关问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ni0224

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值