RabbitMQ exchange不运行时如何处理错误';不存在(消息通过消息网关接口发送)

RabbitMQ exchange不运行时如何处理错误';不存在(消息通过消息网关接口发送),rabbitmq,spring-integration,spring-integration-amqp,Rabbitmq,Spring Integration,Spring Integration Amqp,我想知道在以下情况下处理错误的规范方法是什么(代码是一个最小的工作示例): 消息通过消息网关发送,消息网关定义其defaultRequestChannel和@gateway方法: 消息从通道读取并通过AMQP出站适配器发送: RabbitMQ配置为骨架: 我通常包括一个bean来定义我所依赖的RabbitMQ配置(交换、队列和绑定),它实际上运行良好。但是在测试失败场景时,我发现了一个我不知道如何使用Spring集成正确处理的情况。这些步骤是: 删除配置RabbitMQ的bean

我想知道在以下情况下处理错误的规范方法是什么(代码是一个最小的工作示例):

  • 消息通过消息网关发送,消息网关定义其
    defaultRequestChannel
    @gateway
    方法:
  • 消息从通道读取并通过AMQP出站适配器发送:
  • RabbitMQ配置为骨架:
我通常包括一个bean来定义我所依赖的RabbitMQ配置(交换、队列和绑定),它实际上运行良好。但是在测试失败场景时,我发现了一个我不知道如何使用Spring集成正确处理的情况。这些步骤是:

  • 删除配置RabbitMQ的bean
  • 针对未配置的vanilla RabbitMQ实例运行流
我所期望的是:

  • 无法传递邮件,因为找不到exchange
  • 要么我找到某种方法从调用者线程上的消息传递网关获取异常
  • 要么我想办法截获这个错误
我发现:

  • 无法传递消息,因为找不到exchange,而且每次调用
    @Gateway
    方法时都会记录此错误消息
2020-02-11 08:18:40.746错误42778---[127.0.0.1:5672]o.s.a.r.c.CachingConnectionFactory:通道关闭:通道错误;协议方法:#方法(回复代码=404,回复文本=未找到-vhost'/'中没有交换'my.exchange',类id=60,方法id=40)
  • 网关没有出现故障,我也没有找到配置网关的方法(例如:向接口方法添加
    抛出
    子句,配置事务通道,设置
    等待确认
    确认超时
  • 我还没有找到一种方法来捕获
    CachingConectionFactory
    错误(例如:配置事务通道)
  • 我还没有找到在另一个通道(在网关的
    errorChannel
    上指定)或Spring Integration的默认
    errorChannel
    中捕获错误消息的方法
我理解这样的故障可能不会通过消息传递网关向上游传播,消息传递网关的任务是将调用者与消息传递API隔离,但我确实希望这样的错误是可拦截的

你能给我指一下正确的方向吗


谢谢。

RabbitMQ本质上是异步的,这也是它性能如此出色的原因之一

但是,您可以通过启用确认和返回并设置此选项来阻止调用方:

/**
 * Set to true if you want to block the calling thread until a publisher confirm has
 * been received. Requires a template configured for returns. If a confirm is not
 * received within the confirm timeout or a negative acknowledgment or returned
 * message is received, an exception will be thrown. Does not apply to the gateway
 * since it blocks awaiting the reply.
 * @param waitForConfirm true to block until the confirmation or timeout is received.
 * @since 5.2
 * @see #setConfirmTimeout(long)
 * @see #setMultiSend(boolean)
 */
public void setWaitForConfirm(boolean waitForConfirm) {
    this.waitForConfirm = waitForConfirm;
}
(使用DSL
.waitForConfirm(true)

这还需要一个确认相关性表达式。下面是一个来自其中一个测试用例的示例

    @Bean
    public IntegrationFlow flow(RabbitTemplate template) {
        return f -> f.handle(Amqp.outboundAdapter(template)
                .exchangeName("")
                .routingKeyFunction(msg -> msg.getHeaders().get("rk", String.class))
                .confirmCorrelationFunction(msg -> msg)
                .waitForConfirm(true));
    }

    @Bean
    public CachingConnectionFactory cf() {
        CachingConnectionFactory ccf = new CachingConnectionFactory(
                RabbitAvailableCondition.getBrokerRunning().getConnectionFactory());
        ccf.setPublisherConfirmType(CachingConnectionFactory.ConfirmType.CORRELATED);
        ccf.setPublisherReturns(true);
        return ccf;
    }

    @Bean
    public RabbitTemplate template(ConnectionFactory cf) {
        RabbitTemplate rabbitTemplate = new RabbitTemplate(cf);
        rabbitTemplate.setMandatory(true);               // for returns
        rabbitTemplate.setReceiveTimeout(10_000);
        return rabbitTemplate;
    }

请记住,这将大大降低速度(类似于使用事务),因此您可能需要重新考虑是否希望在每次发送时都这样做(除非性能不是问题)。

谢谢@gary russel,您的回答为我指明了正确的方向。AFAICT,publisher确认使用RabbitMQ在发布方提供数据安全的首选方式。这比仅仅将通道标记为已事务处理要复杂得多,但即使是粗略的初步测量也表明publisher确认比事务处理要轻。这是事实,但最好的性能是当您发送一组消息并稍后等待确认时,而不是一次一个;但是对于您的用例,如果您想“立即”得到一个异常,您别无选择。
@Configuration
public class RabbitMqConfiguration
{
    @Autowired
    private ConnectionFactory rabbitConnectionFactory;

    @Bean
    public RabbitTemplate myTemplate()
    {
        RabbitTemplate r = new RabbitTemplate(rabbitConnectionFactory);
        r.setExchange(INPUT_QUEUE_NAME);
        r.setConnectionFactory(rabbitConnectionFactory);
        return r;
    }
}
2020-02-11 08:18:40.746 ERROR 42778 --- [ 127.0.0.1:5672] o.s.a.r.c.CachingConnectionFactory       : Channel shutdown: channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no exchange 'my.exchange' in vhost '/', class-id=60, method-id=40)
/**
 * Set to true if you want to block the calling thread until a publisher confirm has
 * been received. Requires a template configured for returns. If a confirm is not
 * received within the confirm timeout or a negative acknowledgment or returned
 * message is received, an exception will be thrown. Does not apply to the gateway
 * since it blocks awaiting the reply.
 * @param waitForConfirm true to block until the confirmation or timeout is received.
 * @since 5.2
 * @see #setConfirmTimeout(long)
 * @see #setMultiSend(boolean)
 */
public void setWaitForConfirm(boolean waitForConfirm) {
    this.waitForConfirm = waitForConfirm;
}
    @Bean
    public IntegrationFlow flow(RabbitTemplate template) {
        return f -> f.handle(Amqp.outboundAdapter(template)
                .exchangeName("")
                .routingKeyFunction(msg -> msg.getHeaders().get("rk", String.class))
                .confirmCorrelationFunction(msg -> msg)
                .waitForConfirm(true));
    }

    @Bean
    public CachingConnectionFactory cf() {
        CachingConnectionFactory ccf = new CachingConnectionFactory(
                RabbitAvailableCondition.getBrokerRunning().getConnectionFactory());
        ccf.setPublisherConfirmType(CachingConnectionFactory.ConfirmType.CORRELATED);
        ccf.setPublisherReturns(true);
        return ccf;
    }

    @Bean
    public RabbitTemplate template(ConnectionFactory cf) {
        RabbitTemplate rabbitTemplate = new RabbitTemplate(cf);
        rabbitTemplate.setMandatory(true);               // for returns
        rabbitTemplate.setReceiveTimeout(10_000);
        return rabbitTemplate;
    }