如何对我所有的队列进行常见的死信交换

时间:2018-06-30 13:09:36

标签: spring spring-boot rabbitmq

我有一个通用的库来处理RabbitMQ的所有配置,我的目标是实现一种通用的策略以在某些尝试后处理错误消息,并且在我心目中的交换可以接收所有死信消息,并且通过使标头具有原始队列名称来区分每个消息。经过多次尝试和重试,这一切。

我不知道如何实现这种方法,您的帮助将有助于实现这一目标!

这是我的一名常客

    @RabbitListener(bindings = @QueueBinding(
        value = @Queue(value = "${swift.queue.driverOnlineStatus}", durable = "true"),
        exchange = @Exchange(value = "${swift.queue.driverExchange}",
            type = ExchangeTypes.HEADERS,
            ignoreDeclarationExceptions = "true"),
        arguments = {
            @Argument(name = "x-match", value = "all"),
            @Argument(name = "eventName", value = "${swift.queue.driverOnlineStatus}"),
        },
        key = ""))

这里是配置

@Bean
@ConditionalOnMissingBean
@ConditionalOnClass(value = {ConnectionFactory.class})
public SimpleMessageListenerContainer simpleMessageListenerContainerForAsyncRabbit(ConnectionFactory connectionFactoryfactory,
                                                                           @Autowired(required = false) ErrorHandler errorHandler,
                                                                           RabbitTemplate rabbitTemplate) {
    log.debug("creating new SimpleMessageListenerContainer ... for async rabbit: {}", rabbitProperties);
    SimpleMessageListenerContainer containerF = new SimpleMessageListenerContainer(connectionFactoryfactory);
    containerF.setConnectionFactory(connectionFactoryfactory);
    if (errorHandler != null) {
        containerF.setErrorHandler(errorHandler);
    }
    //listener retry
    Interceptor retryListenerInterceptor = RetryInterceptorBuilder.stateless()
            .maxAttempts(rabbitProperties.getListener().getSimple().getRetry().getMaxAttempts())
            .backOffOptions(rabbitProperties.getListener().getSimple().getRetry().getInitialInterval(),rabbitProperties.getListener().getSimple().getRetry().getMultiplier(),rabbitProperties.getListener().getSimple().getRetry().getMaxInterval())
            .recoverer(new RejectAndDontRequeueRecoverer())
            .build();

    /*
        Note on the two below interceptors
       use a non-transactional template for the DLQ
     When retries reach the maximum number that massage is dead-lettered
     */

    //listener try to republish to error exchange
    /*Interceptor retryListenerErrorInterceptor = RetryInterceptorBuilder.stateless()
            .maxAttempts(rabbitProperties.getListener().getSimple().getRetry().getMaxAttempts())
            .backOffOptions(rabbitProperties.getListener().getSimple().getRetry().getInitialInterval(),rabbitProperties.getListener().getSimple().getRetry().getMultiplier(),rabbitProperties.getListener().getSimple().getRetry().getMaxInterval())
            .recoverer(new RepublishMessageRecoverer(rabbitTemplate,innodevProp.getEvent().getErrorExchangeName()))
            .build();
    */

    //publisher retry
    Interceptor retryPublisherInterceptor = RetryInterceptorBuilder.stateless()
            .maxAttempts(rabbitProperties.getTemplate().getRetry().getMaxAttempts())
            .backOffOptions(rabbitProperties.getTemplate().getRetry().getInitialInterval(),rabbitProperties.getTemplate().getRetry().getMultiplier(),rabbitProperties.getTemplate().getRetry().getMaxInterval())
            .recoverer(new RepublishMessageRecoverer(rabbitTemplate,innodevProp.getEvent().getErrorExchangeName()))
            .build();

    containerF.setAdviceChain(retryListenerInterceptor,retryPublisherInterceptor);
    return containerF;
}

我不确定是否需要定义SimpleMessageListenerContainer,我认为我们应该依靠@EnableRabbit

我了解您的方法,我们应该为retryListenerInterceptor创建一个具有RepublishMessageRecoverer并且可以完成工作的Bean

2 个答案:

答案 0 :(得分:0)

RepublishMessageRecoverer完全可以满足您的需求。传递重试用尽后,它将消息发送到带有以下附加头的DLX:

public static final String X_EXCEPTION_STACKTRACE = "x-exception-stacktrace";
public static final String X_EXCEPTION_MESSAGE = "x-exception-message";
public static final String X_ORIGINAL_EXCHANGE = "x-original-exchange";
public static final String X_ORIGINAL_ROUTING_KEY = "x-original-routingKey";

您可以将所有消息路由到同一队列,也可以将DLQ与原始路由密钥绑定。

请参见the documentation

  

RepublishMessageRecoverer在消息头中发布消息以及其他信息,例如异常消息,堆栈跟踪,原始交换和路由键。通过创建子类并覆盖additionalHeaders(),可以添加其他标题。也可以在additionalHeaders()方法中更改deliveryMode(或其他任何属性):

答案 1 :(得分:0)

通过添加以下属性,我发现spring-boot对简单容器具有开箱即用的重试支持:

spring.rabbitmq.listener.simple.retry.enabled=true

这将启用重试功能,如果您未设置其他与重试相​​关的属性将采用默认值。另外,您应该确保禁用重新排队功能

spring.rabbitmq.listener.simple.default-requeue-rejected=false

现在您应该通过添加以下代码来定义RepublishMessagRecoverer

@BeanmatchIfMissing = true)
public RepublishMessageRecoverer republishMessageRecoverer(RabbitTemplate rabbitTemplate) {
    return new RepublishMessageRecoverer(rabbitTemplate,"error-exchange");
}

@Bean
public RetryOperationsInterceptor rabbitRetryInterceptor(RabbitTemplate rabbitTemplate) {
    log.debug("creating retry operation for rabbitmq ");
    return RetryInterceptorBuilder.stateless()
            .recoverer(republishMessageRecoverer(rabbitTemplate))
            .build();
}

现在所有默认容器都将具有此配置,并且如果重试已用尽,则消息将通过这些标头发送到retryInterceptor中定义的交换机

public static final String X_EXCEPTION_STACKTRACE = "x-exception-stacktrace";
public static final String X_EXCEPTION_MESSAGE = "x-exception-message";  
public static final String X_ORIGINAL_EXCHANGE = "x-original-exchange";
public static final String X_ORIGINAL_ROUTING_KEY = "x-original-routingKey";

附加说明: 如果您希望其他队列具有不同的行为,则可以定义另一个listenerContainer并将其分配给侦听器

**示例:**

@Bean
RabbitListenerContainerFactory dispatchConnection(ConnectionFactory connectionFactory){
    System.out.println("creating the container");
    SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
    factory.setConnectionFactory(connectionFactory);
    factory.setDefaultRequeueRejected(true);

    return factory;
}

并按照以下示例将容器分配给@RabbitListener

@RabbitListener(containerFactory ="beanListenerContainerName",  queues = "queueName")

我还要感谢加里·罗素(Gary Russell)的回答。