spring amqp setConfirmCallback的问题

时间:2018-01-30 09:44:46

标签: rabbitmq spring-amqp

我配置像打击一样的兔子模板

@Autowired
public Sender(RabbitTemplate rabbitTemplate) {
    //消息是否到达交换机的回调
    rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
        if (!ack) {
            log.info("sender not send message to the right exchange" + " correlationData=" + correlationData + " ack=" + ack + " cause" + cause);
        } else {
            log.info("sender send message to the right exchange" + " correlationData=" + correlationData + " ack=" + ack + " cause" + cause);
        }
    });
    //消息是否到达正确的消息队列,如果没有会把消息返回
    rabbitTemplate.setReturnCallback((message, replyCode, replyText, tmpExchange, tmpRoutingKey) -> {
        log.info("Sender send message failed: " + message + " " + replyCode + " " + replyText + " " + tmpExchange + " " + tmpRoutingKey);
        //try to resend msg
    });

    RetryTemplate retryTemplate = new RetryTemplate();
    ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
    backOffPolicy.setInitialInterval(500);
    backOffPolicy.setMultiplier(10.0);
    backOffPolicy.setMaxInterval(10000);
    retryTemplate.setBackOffPolicy(backOffPolicy);
    rabbitTemplate.setRetryTemplate(retryTemplate);
    rabbitTemplate.setMandatory(true);
    this.rabbitTemplate = rabbitTemplate;

}

和发送方法

  public void send() {
        System.out.println("sender is sending message");
        String uuid1 = UUID.randomUUID().toString();
        String uuid2 = UUID.randomUUID().toString();
        String uuid3 = UUID.randomUUID().toString();
        System.out.println("UUID="+uuid1+"---"+uuid2+"---"+uuid3);
        // the right excharge name and routing key 
        rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_NAME, "aaa.orange.bbb", "hello,world1 2", new CorrelationData(uuid1));
         // wrong exchage name 
        rabbitTemplate.convertAndSend("测试交换机名", "aaa.orange.ccc", "测试错误的交换机名", new CorrelationData(uuid2));
        // wrong excharge name  
        rabbitTemplate.convertAndSend("测试交换机名", "1111111", "测试错误的队列名", new CorrelationData(uuid3));

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

我的问题是我只编码  rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_NAME," aaa.orange.bbb"," hello,world1 2",new CorrelationData(uuid1));

评论两行

  rabbitTemplate.convertAndSend("测试交换机名", "aaa.orange.ccc", "测试错误的交换机名", new CorrelationData(uuid2));
            // wrong excharge name  
            rabbitTemplate.convertAndSend("测试交换机名", "1111111", "测试错误的队列名", new CorrelationData(uuid3));

confirmCallback日志是"发送者向正确的交换机发送消息"

但如果我一次发送三条消息,则confirmCallback日志为 三个"发件人不向正确的交换机发送消息"记录和我检查队列,正确的消息是发送到队列,我该如何解决这个问题

1 个答案:

答案 0 :(得分:0)

你的问题不明确;如果你的意思是你要发送到一个不存在的交易所 - 这对于该频道来说是致命的,所以任何未决的确认都将丢失。

由于Spring AMQP缓存了重用的通道,下游操作可能导致通道关闭,确认丢失。

例如:

@SpringBootApplication
public class So48518319Application {

    public static void main(String[] args) {
        SpringApplication.run(So48518319Application.class, args).close();
    }

    @Bean
    public ApplicationRunner runner(RabbitTemplate template) {
        return args -> {
            template.setConfirmCallback((correlation, ack, cause) -> {
                System.out.println(correlation + ":" + ack + " " + (cause == null ? "" : cause));
                ((MyCorrelationData) correlation).getLatch().countDown();
            });
            MyCorrelationData foo = new MyCorrelationData("foo");
            MyCorrelationData bar = new MyCorrelationData("bar");
            MyCorrelationData baz = new MyCorrelationData("baz");
            template.convertAndSend("output", "output.foo", "foo", foo);
            template.convertAndSend("output", "output.foo", "foo", bar);
            template.convertAndSend("output", "output.foo", "foo", baz);
            if (!foo.getLatch().await(10, TimeUnit.SECONDS)) {
                throw new RuntimeException("Foo failed");
            }
            if (!bar.getLatch().await(10, TimeUnit.SECONDS)) {
                throw new RuntimeException("Bar failed");
            }
            if (!baz.getLatch().await(10, TimeUnit.SECONDS)) {
                throw new RuntimeException("Baz failed");
            }
            System.out.println("All good");
        };
    }

    public static class MyCorrelationData extends CorrelationData {

        private CountDownLatch latch = new CountDownLatch(1);

        public MyCorrelationData(String id) {
            super(id);
        }

        protected CountDownLatch getLatch() {
            return this.latch;
        }

        protected void setLatch(CountDownLatch latch) {
            this.latch = latch;
        }

    }

}

运作良好

CorrelationData [id=foo]:true 
CorrelationData [id=bar]:true 
CorrelationData [id=baz]:true 
All good

但如果我将其更改为

            template.convertAndSend("output", "output.foo", "foo", foo);
            template.convertAndSend("noutput", "output.foo", "foo", bar);
            template.convertAndSend("noutput", "output.foo", "foo", baz);

我们得到了

CorrelationData [id=foo]:false channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no exchange 'noutput' in vhost '/', class-id=60, method-id=40)
CorrelationData [id=bar]:false channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no exchange 'noutput' in vhost '/', class-id=60, method-id=40)
CorrelationData [id=baz]:false channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no exchange 'noutput' in vhost '/', class-id=60, method-id=40)

为避免在收到确认之前重复使用频道,您可以使用模板的invoke方法;这可以防止频道被重用于坏发送:

@SpringBootApplication
public class So48518319Application {

    public static void main(String[] args) {
        SpringApplication.run(So48518319Application.class, args).close();
    }

    @Bean
    public ApplicationRunner runner(RabbitTemplate template) {
        return args -> {
            template.setConfirmCallback((correlation, ack, cause) -> {
                System.out.println(correlation + ":" + ack + " " + (cause == null ? "" : cause));
                MyCorrelationData myCorrelation = (MyCorrelationData) correlation;
                myCorrelation.getLatch().countDown();
                myCorrelation.setAck(ack);
            });
            MyCorrelationData foo = new MyCorrelationData("foo");
            MyCorrelationData bar = new MyCorrelationData("bar");
            MyCorrelationData baz = new MyCorrelationData("baz");
            boolean result1 = template.invoke(t -> {
                t.convertAndSend("output", "output.foo", "foo", foo);
                try {
                    if (!foo.getLatch().await(10, TimeUnit.SECONDS)) {
                        throw new RuntimeException("Foo failed");
                    }
                }
                catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
                return foo.isAck();
            });
            boolean result2 = template.invoke(t -> {
                t.convertAndSend("noutput", "output.foo", "bar", bar);
                try {
                    if (!bar.getLatch().await(10, TimeUnit.SECONDS)) {
                        throw new RuntimeException("Bar failed");
                    }
                }
                catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
                return bar.isAck();
            });
            boolean result3 = template.invoke(t -> {
                t.convertAndSend("noutput", "output.foo", "baz", baz);
                try {
                    if (!baz.getLatch().await(10, TimeUnit.SECONDS)) {
                        throw new RuntimeException("Baz failed");
                    }
                }
                catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
                return baz.isAck();
            });
            System.out.println("All done: " + result1 + "," + result2 + "," + result3);
        };
    }

    public static class MyCorrelationData extends CorrelationData {

        private final CountDownLatch latch = new CountDownLatch(1);

        private volatile boolean ack;

        public MyCorrelationData(String id) {
            super(id);
        }

        public CountDownLatch getLatch() {
            return this.latch;
        }

        public boolean isAck() {
            return this.ack;
        }

        public void setAck(boolean ack) {
            this.ack = ack;
        }

    }

}

CorrelationData [id=foo]:true 
CorrelationData [id=bar]:false channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no exchange 'noutput' in vhost '/', class-id=60, method-id=40)
CorrelationData [id=baz]:false channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no exchange 'noutput' in vhost '/', class-id=60, method-id=40)
All done: true,false,false

但这会失败使用发布商确认的好处,除非你在不同的线程上进行发送。

底线是“如果您使用确认,请不要向不存在的交易所发送消息”。