如何配置“端到端”发布者使用Spring AMQP确认

时间:2016-10-15 11:45:35

标签: rabbitmq spring-amqp

我想在RabbitMQ和Spring AMQP中使用发布者确认,如果侦听器在处理消息期间抛出异常,则消息确认回调将获得NACK。

关注this blog post,我说的是用红色标记的用例:

enter image description here

主要问题是:

  1. 如何配置ConnectionFactory,RabbitTemplate和ListenerContainer以启用手动NACK?

  2. 我在侦听器中需要做什么才能在发生异常的情况下NACK消息并使用success = false调用确认回调?

  3. 这是我的豆子:

    @Bean
    public ConnectionFactory connectionFactory() {
        CachingConnectionFactory connectionFactory = new CachingConnectionFactory("localhost");
        connectionFactory.setPublisherConfirms(true);
        return connectionFactory;
    }
    
    @Bean
    public ConfirmCallback confirmCallback() {
        return new ConfirmCallbackTestImplementation();
    }
    
    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory, ConfirmCallback confirmCallback) {
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        rabbitTemplate.setConfirmCallback(confirmCallback);
        rabbitTemplate.setExchange(DIRECT_EXCHANGE);
        return rabbitTemplate;
    }
    
    @Bean
    public FaultyMessageListener faultyListener(RabbitAdmin rabbitAdmin, DirectExchange exchange, ConnectionFactory connectionFactory) {
        Queue queue = queue(rabbitAdmin, exchange, "faultyListener");
        FaultyMessageListener listener = new FaultyMessageListener();
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
        container.setMessageListener(listener);
        container.setQueues(queue);
        container.setDefaultRequeueRejected(false);
        container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
        container.start();
        return listener;
    }
    
    private Queue queue(RabbitAdmin rabbitAdmin, DirectExchange exchange, String routingKey) {
        Queue queue = new Queue(routingKey, true, false, true);
        rabbitAdmin.declareQueue(queue);
        rabbitAdmin.declareBinding(BindingBuilder.bind(queue).to(exchange).with(routingKey));
        return queue;
    }
    

    这是我的Listener实现:

    public class FaultyMessageListener implements ChannelAwareMessageListener {
    
        private final List<Message> receivedMessages = new ArrayList<>();
    
        private final CountDownLatch latch = new CountDownLatch(1);
    
        @Override
        public void onMessage(Message message, Channel channel) throws Exception {
            receivedMessages.add(message);
            channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
            latch.countDown();
            throw new AmqpException("Message could not be processed");
        }
    
    }
    

    这是我的确认回电:

    public static class ConfirmCallbackTestImplementation implements ConfirmCallback {
    
        private volatile Map<String, Boolean> confirmations = new HashMap<>();
        private volatile HashMap<String, CountDownLatch> expectationLatches = new HashMap<>();
    
        @Override
        public void confirm(CorrelationData correlationData, boolean success, String s) {
            confirmations.put(correlationData.getId(), success);
            expectationLatches.get(correlationData.getId()).countDown();
        }
    
        public CountDownLatch expect(String correlationId) {
            CountDownLatch latch = new CountDownLatch(1);
            this.expectationLatches.put(correlationId, latch);
            return latch;
        }
    
    }
    

    然后我使用以下测试来验证所需的行为:

    @Autowired
    private RabbitTemplate template;
    
    @Autowired
    private FaultyMessageListener faultyListener;
    
    @Autowired
    private ConfirmCallbackTestImplementation testConfirmCallback;
    
    @Test
    public void sendMessageToFaultyMessageListenerResultsInNack() throws InterruptedException {
        String correlationId = "corr-data-test-2";
        CountDownLatch confirmationLatch = testConfirmCallback.expect(correlationId);
    
        template.convertAndSend("ConnectionsTests.PublisherConfirm", "faultyListener", "faulty message", new CorrelationData(correlationId));
    
        assertTrue(faultyListener.latch.await(1, TimeUnit.SECONDS));
        confirmationLatch.await(1, TimeUnit.SECONDS);
    
        assertThat(faultyListener.receivedMessages.size(), is(1));
        assertThat(testConfirmCallback.confirmations.get(correlationId), is(false));
    }
    

    测试结果如下:

    java.lang.AssertionError: 
        Expected: is <false>
             but: was <true>
    

    为最后一个断言。对我而言,这类似于确认回调总是使用success = true而不是success = false来调用我对听众channel.basicNack(...)的期望。

1 个答案:

答案 0 :(得分:3)

它没有那样的工作;发布者方面的ack / nack纯粹是经纪人是否已接受该消息。实际上,很少返回nack,因为它意味着代理本身存在问题 - 请参阅the rabbit documentation

  只有在负责队列的Erlang进程中发生内部错误时,才会发送

basic.nack。

同样,消费者方面的ack / nack纯粹是关于消费者是否已接受对消息的责任,而nack允许消息被重新排队,丢弃或路由到死信队列。

消息发布后,消费者无法与发布者进行通信。如果您需要此类通信,则需要设置回复队列。

如果您希望发布商和消费者之间存在紧密联系,则可以使用Spring Remoting (RPC) Over RabbitMQ。如果使用者抛出异常,它将被传播回发布者 - 但是,该机制仅支持Java Serializable对象。

虽然文档引用了XML,但您可以将代理和服务调用程序连接起来@Bean