来自AmqpInboundChannelAdapter的Manualy NACK消息

时间:2018-01-23 12:51:34

标签: java spring spring-integration spring-amqp

这是我目前的代码:

@Bean
public IntegrationFlow someFlow() {
    return IntegrationFlows
            .from(someInboundAdapter())
            .transform(new JsonToObjectTransformer(SomeObject.class))
            .filter((SomeObject s) -> s.getId()!=null && s.getId().isRealId(), f -> f.discardChannel(manualNackChannel()))
            .channel(amqpInputChannel())
            .get();
}

@ServiceActivator(inputChannel = "manualNackChannel")
public void manualNack(@Header(AmqpHeaders.CHANNEL) Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) Long tag) throws IOException {
    channel.basicNack(tag, false, false);
}

@Bean
public AmqpInboundChannelAdapter someInboundAdapter() {
    AmqpInboundChannelAdapter adapter = new AmqpInboundChannelAdapter(someListenerContainer());
    adapter.setErrorChannel(manualNackChannel());   //NOT WORKING
    return adapter;
}

@Bean
public SimpleMessageListenerContainer someListenerContainer() {
    SimpleMessageListenerContainer listenerContainer = new SimpleMessageListenerContainer(commonConfig.connectionFactory());
    listenerContainer.setQueues(someQueue());
    listenerContainer.setConcurrentConsumers(4);
    listenerContainer.setMessageConverter(jackson2JsonConverter());
    listenerContainer.setAcknowledgeMode(AcknowledgeMode.MANUAL);
    listenerContainer.setConsumerTagStrategy(consumerTagStrategy());
    listenerContainer.setAfterReceivePostProcessors(new GUnzipPostProcessor());
    listenerContainer.setAdviceChain(commonConfig.retryInterceptor());  //reties 3 times and RejectAndDontRequeueRecoverer
    return listenerContainer;
}

这里我使用MANUAL ACK-ing,因为我只想在IntegrationFlow的最后部分处理成功时才想要ACK / NACK消息。

这里,如果无法反序列化消息,则调用retryInterceptor,但在完成所有重试之后,我需要能够手动NACK消息。我希望在适配器上使用setErrorChannel方法执行此操作,但我无法在manualNack中获取AMQP通道标头。

这是从AmqpInboundChannelAdapter手动NACK消息的正确方法吗?

更新

我想这是我目前的解决方案,但不知道是否足够好:

private ErrorMessageStrategy nackStrategy(){
    return (throwable, attributes) -> {
        Object inputMessage = attributes.getAttribute(ErrorMessageUtils.INPUT_MESSAGE_CONTEXT_KEY);
        return new ErrorMessage(throwable, ((Message)inputMessage).getHeaders());
    };
}

@Bean
public AmqpInboundChannelAdapter someInboundAdapter() {
    AmqpInboundChannelAdapter adapter = new AmqpInboundChannelAdapter(someListenerContainer());
    adapter.setRecoveryCallback(new ErrorMessageSendingRecoverer(manualNackChannel(), nackStrategy()));
    adapter.setRetryTemplate(commonConfig.retryTemplate());
    return adapter;
}

2 个答案:

答案 0 :(得分:0)

  

如果邮件无法反序列化

由于无法反序列化AMQP消息,因此未创建Spring Message,因此没有AmqpHeaders.CHANNEL标题。

我不确定ErrorMessageSendingRecoverer如何帮助您,因为反序列化确实在SimpleMessageListenerContainer级别发生,比onMessage()中的AmqpInboundChannelAdapter早一点。< / p>

还不确定如何帮助你,但也许你可以分享一些简单的Spring Boot项目从我们这边玩?感谢

答案 1 :(得分:0)

以下是此示例的完整工作代码。您可以在3个REST端点上测试ACK / NACK:

http://localhost:8080/sendForAck - &gt;将对象SomeObject发送到队列proba,转换它,转发到probaEx并在之后确认

http://localhost:8080/sendForNack - &gt;将发送格式错误的byte[]消息,该消息无法反序列化并且将被NACK-ed。

http://localhost:8080/sendForNack2 - &gt;将创建格式错误的json消息,并将使用InvalidFormatException

进行NACK编辑
@Controller
@EnableAutoConfiguration
@Configuration
public class SampleController {

    @Autowired
    public RabbitTemplate rabbitTemplate;

    @RequestMapping("/sendForAck")
    @ResponseBody
    String sendForAck() {
        SomeObject s = new SomeObject();
        s.setId(2);
        rabbitTemplate.convertAndSend("", "proba", s);
        return "Sent for ACK!";
    }

    @RequestMapping("/sendForNack")
    @ResponseBody
    String sendForNack() {
        rabbitTemplate.convertAndSend("", "proba", new byte[]{1,2,3});
        return "Sent for NACK!";
    }

    @RequestMapping("/sendForNack2")
    @ResponseBody
    String sendForNack2() {
        MessageProperties p = new MessageProperties();
        p.getHeaders().put("__TypeId__", "SampleController$SomeObject");
        p.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
        p.setPriority(0);
        p.setContentEncoding("UTF-8");
        p.setContentType("application/json");
        rabbitTemplate.send("", "proba", new org.springframework.amqp.core.Message("{\"id\":\"abc\"}".getBytes(), p));
        return "Sent for NACK2!";
    }

    static class SomeObject{
        private Integer id;
        public Integer getId(){return id;}
        public void setId(Integer id){ this.id=id; }

        @Override
        public String toString() {
            return "SomeObject{" +
                    "id=" + id +
                    '}';
        }
    }

    @Bean
    public IntegrationFlow someFlow() {
        return IntegrationFlows
                .from(someInboundAdapter())
                .transform(new JsonToObjectTransformer(SomeObject.class))
                .filter((SomeObject s) -> s.getId()!=null, f -> f.discardChannel(manualNackChannel()))
                .transform((SomeObject s) -> {s.setId(s.getId()*2); return s;})
                .handle(amqpOutboundEndpoint())
                .get();
    }

    @Bean
    public MessageChannel manualNackChannel() {
        return new DirectChannel();
    }

    @Bean
    public MessageChannel manualAckChannel() {
        return new DirectChannel();
    }

    @ServiceActivator(inputChannel = "manualNackChannel")
    public void manualNack(@Header(AmqpHeaders.CHANNEL) Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) Long tag, @Payload Object p) throws IOException {
        channel.basicNack(tag, false, false);
        System.out.println("NACKED " + p);
    }

    @ServiceActivator(inputChannel = "manualAckChannel")
    public void manualAck(@Header(AmqpHeaders.CHANNEL) Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) Long tag, @Payload Object p) throws IOException {
        channel.basicAck(tag, false);
        System.out.println("ACKED " + p);
    }

    private ErrorMessageStrategy nackStrategy() {
        return (throwable, attributes) -> {
            Message inputMessage = (Message)attributes.getAttribute(ErrorMessageUtils.INPUT_MESSAGE_CONTEXT_KEY);
            return new ErrorMessage(throwable, inputMessage.getHeaders());
        };
    }

    @Bean
    public AmqpInboundChannelAdapter someInboundAdapter() {
        AmqpInboundChannelAdapter adapter = new AmqpInboundChannelAdapter(someListenerContainer());
        adapter.setRecoveryCallback(new ErrorMessageSendingRecoverer(manualNackChannel(), nackStrategy()));
        adapter.setRetryTemplate(retryTemplate());
        return adapter;
    }

    @Bean
    public RetryTemplate retryTemplate() {
        RetryTemplate template = new RetryTemplate();
        ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
        backOffPolicy.setInitialInterval(10);
        backOffPolicy.setMaxInterval(5000);
        backOffPolicy.setMultiplier(4);
        template.setBackOffPolicy(backOffPolicy);
        SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
        retryPolicy.setMaxAttempts(4);
        template.setRetryPolicy(retryPolicy);
        return template;
    }

    @Bean
    public AmqpOutboundEndpoint amqpOutboundEndpoint() {
        AmqpOutboundEndpoint outboundEndpoint = new AmqpOutboundEndpoint(ackTemplate());
        outboundEndpoint.setConfirmAckChannel(manualAckChannel());
        outboundEndpoint.setConfirmCorrelationExpressionString("#root");
        outboundEndpoint.setExchangeName("probaEx");
        return outboundEndpoint;
    }

    @Bean
    public MessageConverter jackson2JsonConverter() {
        return new Jackson2JsonMessageConverter();
    }

    @Bean
    public RabbitTemplate ackTemplate() {
        RabbitTemplate ackTemplate = new RabbitTemplate(connectionFactory());
        ackTemplate.setMessageConverter(jackson2JsonConverter());
        return ackTemplate;
    }

    @Bean
    public Queue someQueue() {
        return QueueBuilder.nonDurable("proba").build();
    }

    @Bean
    public Exchange someExchange(){
        return ExchangeBuilder.fanoutExchange("probaEx").build();
    }

    @Bean
    public ConnectionFactory connectionFactory() {
        CachingConnectionFactory factory = new CachingConnectionFactory();
        factory.setHost("10.10.121.137");
        factory.setPort(35672);
        factory.setUsername("root");
        factory.setPassword("123456");
        factory.setPublisherConfirms(true);
        return factory;
    }

    @Bean
    public SimpleMessageListenerContainer someListenerContainer() {
        SimpleMessageListenerContainer listenerContainer = new SimpleMessageListenerContainer(connectionFactory());
        listenerContainer.setQueues(someQueue());
        listenerContainer.setMessageConverter(jackson2JsonConverter());
        listenerContainer.setAcknowledgeMode(AcknowledgeMode.MANUAL);
        return listenerContainer;
    }

    public static void main(String[] args) throws Exception {
        SpringApplication.run(SampleController.class, args);
    }
}

不过,问题仍然存在,private ErrorMessageStrategy nackStrategy()是否可以用更好的方式编写?