RabbitMQ [生产者 - 处理非路由消息]

本文探讨了RabbitMQ中遇到的非路由消息问题,包括如何有效处理这类消息,通过具体案例展示了实现方法,并附带了相关代码示例及QA环节,帮助读者理解和解决此类问题。

非路由消息

    生产者发布消息时,在默认情况下,并不知道消息是否发布成功,是否正确路由到相应的队列。本文的目的在于如何处理路由失败的消息。

如何处理

1. 设置mandatroy=true;默认情况是false,设置为true的目的是告诉rabbitmq当交换器路由不到合适的队列时,不是将该消息丢失,而且将这个消息返回到生产者(basic.return);
2. 添加ReturnListener监听器;就是一个回调监听器,就是处理失败路由的消息;

案例

代码

package com.rabbitmq.noroutable;

import com.rabbitmq.client.*;

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

/**
 * Created by wangzhiping on 17/7/4.
 */
public class ProducerTest {

    public static void main(String[] args) {

        ConnectionFactory factory = new ConnectionFactory();
        factory.setPort(6002);

        try {
            // 建立一个TCP链接
            Connection conn = factory.newConnection();

            // 开启一个channel
            Channel channel = conn.createChannel();

            // 创建交换器, 非持久化队列
            channel.exchangeDeclare("ex-noroutable", BuiltinExchangeType.TOPIC, false);

            /*
             * 如何让生产者知道知道没有路由到正确队列的情况下,得到通知
             * 1, 发布消息时,明确发的消息失败需要得到相应的通知;mandatory=true;
             * 2, 设置监听事件,并处理消息;
             *
             * 从原理上,是会发送basic.return消息到生产者,我们需要监听这个消息
             */

            // 消息回传是异步的,NIO
            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("replyCode: " + replyCode + ", replyText: " + replyText + ", routingKey: " + routingKey
                            + ", properties: " + properties + ", body: " + new String(body, "UTF-8"));
                }
            });


            // 这里不申明队列,直接发布消息
            channel.basicPublish("ex-noroutable", "", true,false, null, "noroutable".getBytes());

//            channel.close(AMQP.NO_ROUTE, "");
//            conn.close();

        } catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        }
    }
}

QA

这里需要特别注意的是addReturnListener是一个非阻塞回调接口,如果提前使用了channel.close(),会关闭管道导致无法处理回调消息。
<think>我们正在讨论的是RabbitMQ生产者的异常处理逻辑。根据引用,生产者异常主要涉及网络故障导致连接中断的情况。SpringAMQP提供了重试机制来处理这种情况。 用户想手动模拟触发生产者异常处理逻辑(即ex ->逻辑)。我们可以通过以下方式模拟: 1. 断开与RabbitMQ的连接(如关闭RabbitMQ服务或断开网络) 2. 发送消息,此会触发重试机制(如果配置了重试) 但是,用户可能希望看到的是在代码中如何触发异常处理流程,比如在发送消息模拟一个异常,然后观察生产者的确认机制(如Publisher Confirm)或重试机制。 根据引用[2]和[3],我们可以通过两种方式模拟: a) 模拟网络故障:关闭RabbitMQ服务或者断开网络,然后发送消息,此会触发重试(如果配置了重试)。 b) 模拟发送到不存在的交换机:这样会立即触发ReturnCallback(消息路由失败,但注意,发送到不存在的交换机会直接抛出异常,不会触发ReturnCallback,因为ReturnCallback是用于消息到达了交换机但没有路由到队列的情况)。 注意:发送到不存在的交换机,会触发发送端的异常(比如Channel关闭等),从而触发重试机制(如果配置了重试)或者抛出异常。 具体步骤: 1. 配置生产者确认模式(Publisher Confirm)和返回模式(Publisher Return)[^2][^3]。 2. 编写代码,在发送消息指定一个不存在的交换机(或者关闭RabbitMQ服务)来模拟异常。 但是,根据引用[5]中的示例,它发送了一个消息到存在的交换机,但使用了不存在的routingKey,这样会触发ReturnCallback(因为消息被交换机接收了,但是没有路由到队列,所以会被退回)。但是这种情况不会触发发送异常(因为发送过程是成功的,只是路由失败),所以不会触发重试机制。重试机制是在发送消息这个动作失败(比如连接中断)才会触发。 因此,要触发重试机制,我们需要模拟发送消息出现网络故障。有两种方法: 方法一:关闭RabbitMQ服务 方法二:断开网络 但是,这些方法需要手动操作,而且可能影响其他服务。我们可以在测试中通过模拟一个不可达的地址来触发连接超?但是RabbitTemplate配置的是本地的RabbitMQ,所以关闭本地服务即可。 另外,我们还可以通过配置RabbitTemplate的重试机制,然后模拟发送消息出现网络故障,从而观察重试。 配置重试(在application.yml): ```yaml spring: rabbitmq: template: retry: enabled: true max-attempts: 3 # 最大重试次数 initial-interval: 1000ms # 初始重试间隔 ``` 然后,我们编写一个测试,在RabbitMQ服务关闭的情况下发送消息,观察日志,应该会看到重试的日志,并且最终抛出异常(比如AmqpConnectException)。 示例测试代码(参考引用[3]和[5]): ```java @SpringBootTest public class ProducerExceptionTest { @Autowired private RabbitTemplate rabbitTemplate; @Test public void testProducerException() { // 确保此RabbitMQ服务已经关闭 rabbitTemplate.convertAndSend("exchange.direct", "routing.key", "test message"); // 观察控制台日志,应该会看到重试日志,并最终失败 } } ``` 运行此测试前,先关闭RabbitMQ服务,然后运行测试,会触发重试机制。 另外,如果我们想模拟发送到不存在的交换机(但RabbitMQ服务是运行的)会怎样?实际上,发送到不存在的交换机,RabbitMQ会关闭通道(Channel),并返回一个错误。SpringAMQP会收到这个错误,然后抛出AmqpException。如果配置了重试,这个异常也会触发重试吗?根据SpringAMQP的文档,重试机制主要是针对可重试的异常(如网络连接失败),而发送到不存在的交换机属于不可重试的异常(因为立即失败,重试也无济于事),所以不会重试,直接抛出异常。 因此,要测试重试,必须模拟网络故障(关闭RabbitMQ服务)。 总结手动模拟触发生产者异常处理逻辑的步骤: 1. 配置重试(可选,如果没配置,则不会重试,直接抛出异常) 2. 关闭RabbitMQ服务(或断开网络) 3. 执行发送消息的代码 4. 观察日志(重试日志和最终异常) 另外,如果用户想模拟的是生产者的确认机制(Publisher Confirm),那么需要开启确认模式,并设置确认回调。然后,发送消息到存在的交换机,但是不存在的队列(这样会触发确认回调,并且消息会被退回?注意,确认回调只负责确认消息是否被交换机接收,而不管是否路由到队列。路由失败会通过ReturnCallback返回)。所以,我们可以: 1. 开启确认模式(在配置中设置spring.rabbitmq.publisher-confirms=true,但注意新版本是publisher-confirm-type) 2. 设置ConfirmCallback,处理确认结果(成功或失败) 3. 发送消息到一个存在的交换机,但是不存在的routingKey(这样消息会被交换机接收,所以确认回调会收到ack,但同也会触发ReturnCallback,因为无法路由) 或者,发送消息到一个不存在的交换机,这样会触发确认回调的nack(因为交换机不存在,消息无法投递到交换机)。 示例配置(application.yml): ```yaml spring: rabbitmq: host: localhost port: 5672 username: guest password: guest publisher-confirm-type: correlated # 新版本使用这个 publisher-returns: true ``` 然后,在RabbitTemplate中设置ConfirmCallback和ReturnCallback(注意,ReturnCallback需要设置setMandatory(true)才会触发)[^2][^3]。 示例代码: ```java @Configuration public class RabbitConfig { @Autowired private RabbitTemplate rabbitTemplate; @PostConstruct public void init() { rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> { if (ack) { System.out.println("消息成功发送到交换机"); } else { System.out.println("消息发送到交换机失败:" + cause); } }); // 设置当消息路由不到队列返回(而不是丢弃) rabbitTemplate.setMandatory(true); rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> { System.out.println("消息被退回:"); System.out.println("消息内容:" + new String(message.getBody())); System.out.println("交换机:" + exchange); System.out.println("路由键:" + routingKey); System.out.println("原因:" + replyText); }); } } ``` 然后,我们可以写一个测试来模拟: 情况1:发送到不存在的交换机(触发ConfirmCallback的nack) ```java rabbitTemplate.convertAndSend("non-existent-exchange", "routing.key", "message"); ``` 情况2:发送到存在的交换机,但使用不存在的routingKey(触发ConfirmCallback的ack,同触发ReturnCallback) ```java rabbitTemplate.convertAndSend("direct.exchange", "non-existent-routingKey", "message"); ``` 因此,用户可以根据需要选择模拟的场景。 综上所述,手动模拟触发生产者异常处理逻辑的方法: 1. 模拟网络故障(关闭RabbitMQ服务或断开网络)来测试重试机制。 2. 模拟发送到不存在的交换机来测试ConfirmCallback的nack。 3. 模拟发送到存在的交换机但使用不存在的routingKey来测试ConfirmCallback的ack和ReturnCallback。 请根据实际需求选择模拟方式。</think>### 手动模拟触发 RabbitMQ 生产者异常处理逻辑的方法 要手动模拟触发 RabbitMQ 生产者异常处理逻辑(ex -> 逻辑),可通过以下三种方式实现,这些方法基于引用[1][3][5]中提到的生产者确认机制原理: --- #### 方法一:模拟网络故障(最常用) ```java // 测试类中模拟生产者 @SpringBootTest public class ProducerExceptionTest { @Autowired private RabbitTemplate rabbitTemplate; @Test public void simulateNetworkFailure() { // 1. 先关闭RabbitMQ服务或断开网络连接 // 2. 发送消息(会触发重试机制) rabbitTemplate.convertAndSend("test.queue", "模拟网络故障消息"); // 观察日志:将显示连接超和重试记录 // 如:Retry: Attempt=1, Waiting=1000ms } } ``` **触发原理**: 当 RabbitMQ 服务不可用,SpringAMQP 的 `RabbitTemplate` 会自动触发重试机制(需配置重试参数)[^1][^3]。重试失败后会抛出 `AmqpConnectException`。 --- #### 方法二:发送到不存在的交换机 ```java @Test public void simulateInvalidExchange() { // 发送到未声明的交换机 rabbitTemplate.convertAndSend("non-existent-exchange", "", "错误交换机测试"); // 触发流程: // 1. 抛出AmqpException: 无法路由到交换机 // 2. 触发PublisherCallback的confirm()方法返回nack } ``` **触发原理**: RabbitMQ 生产者确认机制会返回 `nack`,表示消息未被交换机接收[^2][^5]。需配置: ```yaml # application.yml spring: rabbitmq: publisher-confirm-type: correlated # 开启确认机制 ``` --- #### 方法三:强制触发 ReturnCallback ```java @Test public void simulateUnroutableMessage() { // 1. 发送到存在的交换机但无效路由键 rabbitTemplate.convertAndSend("valid.exchange", "invalid-routing-key", "测试消息"); // 2. 需配置强制触发ReturnCallback rabbitTemplate.setMandatory(true); // 关键设置 } ``` **触发原理**: 当消息到达交换机但无法路由到任何队列,会触发 `ReturnCallback` 接口[^2][^5]。需实现: ```java @Slf4j @Component public class CustomReturnCallback implements RabbitTemplate.ReturnCallback { @Override public void returnedMessage( Message message, int replyCode, String replyText, String exchange, String routingKey) { log.error("消息退回: exchange={}, routingKey={}, 原因={}", exchange, routingKey, replyText); } } ``` --- ### 完整配置支持 ```yaml # application.yml spring: rabbitmq: host: localhost # 异常处理关键配置 template: retry: enabled: true # 开启重试 max-attempts: 3 # 最大重试次数 initial-interval: 1s # 重试间隔 publisher-confirm-type: correlated # 开启确认机制 publisher-returns: true # 开启退回机制 ``` --- ### 验证异常处理流程 ```mermaid sequenceDiagram 生产者->>+RabbitMQ: 发送消息 alt 网络故障 RabbitMQ-->>生产者: 连接超 生产者->>生产者: 触发重试机制(3次) 生产者-->>异常处理: 抛出AmqpConnectException else 无效交换机 RabbitMQ-->>生产者: 返回nack确认 生产者->>Callback: 执行confirm(false) else 无效路由键 RabbitMQ-->>生产者: 触发ReturnCallback 生产者->>Callback: 执行returnedMessage() end ``` > **测试建议**:使用 `@SpringBootTest` 结合 JUnit 运行上述测试方法,通过日志观察异常触发流程。建议配合 `try-catch` 捕获 `AmqpException` 进行断言验证[^3][^5]。 --- ### 相关问题 1. RabbitMQ 生产者重试机制与消费者重试有何本质区别? 2. 如何区分处理 `Publisher Confirm` 的 `ack` 和 `nack` 场景? 3. 消息退回(ReturnCallback)如何实现自动重新路由? 4. 在高并发场景下,生产者异常处理如何避免雪崩效应?[^4] [^1]: 生产者重试机制原理 [^2]: 生产者确认与退回机制 [^3]: 网络故障模拟方法 [^4]: 消息堆积与异常关联 [^5]: 生产者异常测试实践
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值