Spring-RabbitMQ 生产者消息确认案例分析

测试环境

  1. Springboot 2.7.0
  2. 本机RabbitMQ Server端

Springboot 集成 RabbitMQ

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

案例准备

  1. 推送成功(推送到Exchange并路由到Queue)。
  2. 推送到Exchange,但没有被路由到Queue。
  3. 没有被推送到Exchange。

本次测试分析准备有3个案例,分别在不开启消息确认和开启消息确认的情况下测试程序运行状态和结果。且测试前提是RabbitMQ Server端是可达的,如不可达会直接报错,也就无法进行后续测试了。
Spring-RabbitMQ 消息确认案例分析

案例测试

一、不开启消息确认

配置文件 application.yaml, 仅包含rabbitMQ连接信息。

spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: admin
    password: admin
    virtual-host: my_vhost

生产者:

@Slf4j
@Component
public class SenderDemo {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void send(){
        log.info("======before========");
        CorrelationData correlationData = new CorrelationData();
        rabbitTemplate.convertAndSend("my.test.msg", (Object) "hello world", correlationData);
        log.info("======before========");
    }

}

消费者:

@Slf4j
@Component
public class Consumer {
    
    @RabbitListener(queues = "myQueue")
    public void consume(@Payload String msg){
        log.info("消费者收到消息 --> {}", msg);
    }
}
案例一(推送到Exchange并路由到Queue)测试

配置类:创建Queue,并将其绑定到Exchange。

@Configuration
public class RabbitNoConfirmConfiguration {

    private final static String TOPIC_EXCHANGE = "myExchange";

    private final static String QUEUE_NAME = "myQueue";

    @Bean
    public RabbitAdmin amqpAdmin(ConnectionFactory connectionFactory) {
        return new RabbitAdmin(connectionFactory);
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate template = new RabbitTemplate(connectionFactory);
        template.setMessageConverter(new Jackson2JsonMessageConverter());
        template.setExchange(TOPIC_EXCHANGE);
        return template;
    }

    @Bean
    public TopicExchange topicExchange(){
        return new TopicExchange(TOPIC_EXCHANGE, false, true);
    }
    
    @Bean
    public Queue queue(){
        return new Queue(QUEUE_NAME, false, false, true);
    }
    
    @Bean
    public Binding binding() {
        return BindingBuilder.bind(queue()).to(topicExchange()).with("my.test.*");
    }
}

测试结果:毫无疑问,一切正常。
Spring-RabbitMQ 消息确认案例分析

案例二(推送到Exchange,但没有被路由到Queue)测试

配置类:创建了Queue,但没有绑定到Exchange。

@Configuration
public class RabbitNoConfirmConfiguration {

    private final static String TOPIC_EXCHANGE = "myExchange";

    private final static String QUEUE_NAME = "myQueue";

    @Bean
    public RabbitAdmin amqpAdmin(ConnectionFactory connectionFactory) {
        return new RabbitAdmin(connectionFactory);
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate template = new RabbitTemplate(connectionFactory);
        template.setMessageConverter(new Jackson2JsonMessageConverter());
        template.setExchange(TOPIC_EXCHANGE);
        return template;
    }

    @Bean
    public TopicExchange topicExchange(){
        return new TopicExchange(TOPIC_EXCHANGE, false, true);
    }

    @Bean
    public Queue queue(){
        return new Queue(QUEUE_NAME, false, false, true);
    }
}

测试结果:生产者正常发送消息并结束。消费者没有消费消息,因为消息没有被路由到Queue。
Spring-RabbitMQ 消息确认案例分析

案例三(消息没有到达Exchange)测试

配置类:让生产者发送消息到一个不存在的Exchange。

@Configuration
public class RabbitNoConfirmConfiguration {

    private final static String QUEUE_NAME = "myQueue";

    @Bean
    public RabbitAdmin amqpAdmin(ConnectionFactory connectionFactory) {
        return new RabbitAdmin(connectionFactory);
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate template = new RabbitTemplate(connectionFactory);
        template.setMessageConverter(new Jackson2JsonMessageConverter());
        template.setExchange("not_exist_exchange");
        return template;
    }

    @Bean
    public Queue queue(){
        return new Queue(QUEUE_NAME, false, false, true);
    }

}

测试结果:
生产者正常发送消息并结束。虽然控制台有错误输出,但是是在发送结束后异步打印出来的。

2022-08-02 12:13:36.012  INFO 56200 --- [           main] i.k.j.spring.rabbitmq.sender.SenderDemo  : ======生产者发送开始========
2022-08-02 12:13:36.029  INFO 56200 --- [           main] i.k.j.spring.rabbitmq.sender.SenderDemo  : ======生产者发送结束========
2022-08-02 12:13:36.032 ERROR 56200 --- [ 127.0.0.1:5672] o.s.a.r.c.CachingConnectionFactory       : Shutdown Signal: channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no exchange 'not_exist_exchange' in vhost 'my_vhost', class-id=60, method-id=40)
结论

如果不开启消息确认,只要RabbitMQ连接可达,生产者就不会发现异常。

二、开启Simple消息确认

配置文件:增加confirmType配置。

spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: admin
    password: admin
    virtual-host: my_vhost
    publisher-confirm-type: SIMPLE

生产者:Simple 消息确认模式下,生产者需要使用 rabbitTemplate.invoke 发送消息。

public void simpleConfirmSend(){
    log.info("======生产者发送开始========");
    boolean success = rabbitTemplate.invoke(operations -> {
        rabbitTemplate.convertAndSend("my.test.msg", (Object) "hello world", new CorrelationData());
        try {
            return rabbitTemplate.waitForConfirms(5_1000);
        }catch (RuntimeException e){
            log.error(e.getMessage(), e);
            return false;
        }
    });
    log.info("消息发送成功了吗? --> {}", success);
    log.info("======生产者发送结束========");
}

消费者:

@Slf4j
@Component
public class Consumer {
    
    @RabbitListener(queues = "myQueue")
    public void consume(@Payload String msg){
        log.info("消费者收到消息 --> {}", msg);
    }
}
案例一(推送到Exchange并路由到Queue)测试

配置类:

@Slf4j
@Configuration
public class RabbitConfiguration {
    private final static String TOPIC_EXCHANGE = "myExchange";
    private final static String QUEUE_NAME = "myQueue";
    
    @Bean
    public RabbitAdmin amqpAdmin(ConnectionFactory connectionFactory) {
        return new RabbitAdmin(connectionFactory);
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate template = new RabbitTemplate(connectionFactory);
        template.setMessageConverter(new Jackson2JsonMessageConverter());
        template.setExchange(TOPIC_EXCHANGE);
        return template;
    }

    @Bean
    public TopicExchange topicExchange(){
        return new TopicExchange(TOPIC_EXCHANGE, false, true);
    }

    @Bean
    public Queue queue(){
        return new Queue(QUEUE_NAME, false, false, true);
    }

    @Bean
    public Binding binding() {
        return BindingBuilder.bind(queue()).to(topicExchange()).with("my.test.*");
    }
}

测试结果:生成者发送成功,消费者消费成功。
Spring-RabbitMQ 消息确认案例分析

案例二(推送到Exchange,但没有被路由到Queue)测试

配置类:

@Slf4j
@Configuration
public class RabbitConfiguration {
    private final static String TOPIC_EXCHANGE = "myExchange";

    private final static String QUEUE_NAME = "myQueue";

    @Bean
    public RabbitAdmin amqpAdmin(ConnectionFactory connectionFactory) {
        return new RabbitAdmin(connectionFactory);
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate template = new RabbitTemplate(connectionFactory);
        template.setMessageConverter(new Jackson2JsonMessageConverter());
        template.setExchange(TOPIC_EXCHANGE);
        return template;
    }

    @Bean
    public TopicExchange topicExchange(){
        return new TopicExchange(TOPIC_EXCHANGE, false, true);
    }

    @Bean
    public Queue queue(){
        return new Queue(QUEUE_NAME, false, false, true);
    }
}

测试结果:生产者发送成功,消费者没有消费到消息。
Spring-RabbitMQ 消息确认案例分析

案例三(消息没有到达Exchange)测试

配置类:

@Slf4j
@Configuration
public class RabbitConfiguration {

    private final static String QUEUE_NAME = "myQueue";

    @Bean
    public RabbitAdmin amqpAdmin(ConnectionFactory connectionFactory) {
        return new RabbitAdmin(connectionFactory);
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate template = new RabbitTemplate(connectionFactory);
        template.setMessageConverter(new Jackson2JsonMessageConverter());
        template.setExchange("not_exist_exchange");
        return template;
    }

    @Bean
    public Queue queue(){
        return new Queue(QUEUE_NAME, false, false, true);
    }
}

测试结果:生产者发送失败。
Spring-RabbitMQ 生产者消息确认案例分析

结论

如果开启Simple消息确认,当消息没有送达Exchange的时候,生产者会失败。但是由生产者代码可见,生产者要等到RabbitMQ回调回来之后才能继续往下执行,所有效率不高但是实现简单。

三、开启Correlated消息确认

配置文件:

spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: admin
    password: admin
    virtual-host: my_vhost
    publisher-confirm-type: CORRELATED

生产者:这里生产者就是简单的发送了,成功与失败在回调中异步确定。

public void send(){
    log.info("======生产者发送开始========");
    CorrelationData correlationData = new CorrelationData();
    rabbitTemplate.convertAndSend("my.test.msg", (Object) "hello world", correlationData);
    log.info("======生产者发送结束========");
}

消费者:

@RabbitListener(queues = "myQueue")
public void consume(@Payload String msg){
    log.info("消费者收到消息 --> {}", msg);
}
案例一(推送到Exchange并路由到Queue)测试
@Slf4j
@Configuration
public class RabbitConfiguration {
    private final static String TOPIC_EXCHANGE = "myExchange";
    private final static String QUEUE_NAME = "myQueue";

    @Bean
    public RabbitAdmin amqpAdmin(ConnectionFactory connectionFactory) {
        return new RabbitAdmin(connectionFactory);
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate template = new RabbitTemplate(connectionFactory);
        template.setMessageConverter(new Jackson2JsonMessageConverter());
        template.setExchange(TOPIC_EXCHANGE);


		//增加confirm回调
        template.setConfirmCallback((correlationData, ack, cause) -> {
            log.info("ConfirmCallback:     关联数据:{}", correlationData);
            log.info("ConfirmCallback:     是否成功:{}", ack);
            log.info("ConfirmCallback:     失败原因原因:{}", cause);
        });


        return template;
    }

    @Bean
    public TopicExchange topicExchange(){
        return new TopicExchange(TOPIC_EXCHANGE, false, true);
    }

    @Bean
    public Queue queue(){
        return new Queue(QUEUE_NAME, false, false, true);
    }

    @Bean
    public Binding binding() {
        return BindingBuilder.bind(queue()).to(topicExchange()).with("my.test.*");
    }
}

测试结果:生产者发送成功,消费者消费成功。confirm回调确认结果为true。
Spring-RabbitMQ 生产者消息确认案例分析

案例二(推送到Exchange,但没有被路由到Queue)测试

配置类:

@Slf4j
@Configuration
public class RabbitConfiguration {
    private final static String TOPIC_EXCHANGE = "myExchange";
    private final static String QUEUE_NAME = "myQueue";

    @Bean
    public RabbitAdmin amqpAdmin(ConnectionFactory connectionFactory) {
        return new RabbitAdmin(connectionFactory);
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate template = new RabbitTemplate(connectionFactory);
        template.setMessageConverter(new Jackson2JsonMessageConverter());
        template.setExchange(TOPIC_EXCHANGE);

        template.setConfirmCallback((correlationData, ack, cause) -> {
            log.info("ConfirmCallback:     关联数据:{}", correlationData);
            log.info("ConfirmCallback:     是否成功:{}", ack);
            log.info("ConfirmCallback:     失败原因原因:{}", cause);
        });


        return template;
    }

    @Bean
    public TopicExchange topicExchange() {
        return new TopicExchange(TOPIC_EXCHANGE, false, true);
    }

    @Bean
    public Queue queue() {
        return new Queue(QUEUE_NAME, false, false, true);
    }
}

测试结果:生产者发送成功,消费者没有消费消息。 确认回调结果为true。
Spring-RabbitMQ 生产者消息确认案例分析

案例三(消息没有到达Exchange)测试

配置类:

@Slf4j
@Configuration
public class RabbitConfiguration {
    private final static String QUEUE_NAME = "myQueue";

    @Bean
    public RabbitAdmin amqpAdmin(ConnectionFactory connectionFactory) {
        return new RabbitAdmin(connectionFactory);
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate template = new RabbitTemplate(connectionFactory);
        template.setMessageConverter(new Jackson2JsonMessageConverter());
        template.setExchange("not_exist_exchange");

        template.setConfirmCallback((correlationData, ack, cause) -> {
            log.info("ConfirmCallback:     关联数据:{}", correlationData);
            log.info("ConfirmCallback:     是否成功:{}", ack);
            log.info("ConfirmCallback:     失败原因原因:{}", cause);
        });


        return template;
    }

    @Bean
    public Queue queue() {
        return new Queue(QUEUE_NAME, false, false, true);
    }
}

测试结果:生产者收到发送失败的回调,即判定消息发送失败。
Spring-RabbitMQ 生产者消息确认案例分析
失败原因:

channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no exchange 'not_exist_exchange' in vhost 'my_vhost', class-id=60, method-id=40)
结论

如果开启Correlated消息确认,当消息没有送达Exchange的时候,生产者会失败。这里生产者只管发送消息,所有回调都是异步执行,性能上有了非常大的提升。

四、开启Returns消息确认

上面我们看到,不论 SIMPLE 还是 CORRELATED 消息确认,都只能确认消息是否成功发送到了Exchange,而无法确认消息是否被正常路由到Queue。如果我们需要确保消息必须被路由到Queue,那么就需要配置publisherReturnstrue, 配置publisherReturns=true 的前提是必须开启消息确认,即conformType 必须是SIMPLE 或者CORRELATED 。conformType 默认为NONE。

配置文件:

spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: admin
    password: admin
    virtual-host: my_vhost
    # 消息确认(ACK)
    publisher-confirm-type: CORRELATED #correlated #确认消息已发送到交换机(Exchange)
    publisher-returns: true #确认消息已发送到队列(Queue)

生产者消费者代码同 CORRELATED 确认时一样。

案例一(推送到Exchange并路由到Queue)测试

配置类:添加returns回调相关代码。

@Slf4j
@Configuration
public class RabbitConfiguration {
    private final static String TOPIC_EXCHANGE = "myExchange";
    private final static String QUEUE_NAME = "myQueue";

    @Bean
    public RabbitAdmin amqpAdmin(ConnectionFactory connectionFactory) {
        return new RabbitAdmin(connectionFactory);
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate template = new RabbitTemplate(connectionFactory);
        template.setMessageConverter(new Jackson2JsonMessageConverter());
        template.setExchange(TOPIC_EXCHANGE);


        template.setConfirmCallback((correlationData, ack, cause) -> {
            log.info("ConfirmCallback:     关联数据 -> {}", correlationData);
            log.info("ConfirmCallback:     是否成功 -> {}", ack);
            log.info("ConfirmCallback:     失败原因原因 -> {}", cause);
        });


        template.setMandatory(true);
        template.setReturnsCallback(returned -> {
            log.info("ReturnCallback -> {}", returned);
        });

        return template;
    }

    @Bean
    public TopicExchange topicExchange() {
        return new TopicExchange(TOPIC_EXCHANGE, false, true);
    }

    @Bean
    public Queue queue() {
        return new Queue(QUEUE_NAME, false, false, true);
    }

    @Bean
    public Binding binding() {
        return BindingBuilder.bind(queue()).to(topicExchange()).with("my.test.*");
    }
}

测试结果:生产者回调成功,消费着消费成功。returns回调没有被调用。
Spring-RabbitMQ 生产者消息确认案例分析

案例二(推送到Exchange,但没有被路由到Queue)测试

配置类:

@Slf4j
@Configuration
public class RabbitConfiguration {
    private final static String TOPIC_EXCHANGE = "myExchange";
    private final static String QUEUE_NAME = "myQueue";

    @Bean
    public RabbitAdmin amqpAdmin(ConnectionFactory connectionFactory) {
        return new RabbitAdmin(connectionFactory);
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate template = new RabbitTemplate(connectionFactory);
        template.setMessageConverter(new Jackson2JsonMessageConverter());
        template.setExchange(TOPIC_EXCHANGE);


        template.setConfirmCallback((correlationData, ack, cause) -> {
            log.info("ConfirmCallback:     关联数据 -> {}", correlationData);
            log.info("ConfirmCallback:     是否成功 -> {}", ack);
            log.info("ConfirmCallback:     失败原因原因 -> {}", cause);
        });


        template.setMandatory(true);
        template.setReturnsCallback(returned -> {
            log.info("ReturnCallback -> {}", returned);
        });

        return template;
    }

    @Bean
    public TopicExchange topicExchange() {
        return new TopicExchange(TOPIC_EXCHANGE, false, true);
    }

    @Bean
    public Queue queue() {
        return new Queue(QUEUE_NAME, false, false, true);
    }
}

测试结果:可以看到,confirm回调结果为true。而且return回调也有被执行,且告诉我们路由到Queue失败。
Spring-RabbitMQ 生产者消息确认案例分析

案例三(消息没有到达Exchange)测试

配置类:

@Slf4j
@Configuration
public class RabbitConfiguration {
    private final static String QUEUE_NAME = "myQueue";

    @Bean
    public RabbitAdmin amqpAdmin(ConnectionFactory connectionFactory) {
        return new RabbitAdmin(connectionFactory);
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate template = new RabbitTemplate(connectionFactory);
        template.setMessageConverter(new Jackson2JsonMessageConverter());
        template.setExchange("not_exist_exchange");


        template.setConfirmCallback((correlationData, ack, cause) -> {
            log.info("ConfirmCallback:     关联数据 -> {}", correlationData);
            log.info("ConfirmCallback:     是否成功 -> {}", ack);
            log.info("ConfirmCallback:     失败原因原因 -> {}", cause);
        });


        template.setMandatory(true);
        template.setReturnsCallback(returned -> {
            log.info("ReturnCallback -> {}", returned);
        });


        return template;
    }


    @Bean
    public Queue queue() {
        return new Queue(QUEUE_NAME, false, false, true);
    }

}

测试结果:
可以看到,confirm回调告诉客户端消息发送失败,return回调没有被调用。
Spring-RabbitMQ 生产者消息确认案例分析

结论

returns回调只有在消息正确发送到Exchange,但没有被路由到Queue才会回调。

另:当return回调被调用后,confirm回调中的关联数据中也可以获取到returnedMessage.

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Spring Boot 中使用 RabbitMQ 时,我们可以通过在配置文件中配置绑定关系,将交换机和队列进行绑定。具体步骤如下: 1. 在 application.properties 或 application.yml 中配置 RabbitMQ 连接信息: ``` spring.rabbitmq.host=localhost spring.rabbitmq.port=5672 spring.rabbitmq.username=guest spring.rabbitmq.password=guest ``` 2. 创建一个交换机和一个队列,并将它们绑定在一起: ``` @Configuration public class RabbitConfig { @Bean public Queue queue() { return new Queue("myqueue", true); } @Bean public DirectExchange exchange() { return new DirectExchange("myexchange"); } @Bean public Binding binding(Queue queue, DirectExchange exchange) { return BindingBuilder.bind(queue).to(exchange).with("mykey"); } } ``` 3. 在需要发送消息的地方,注入 RabbitTemplate 并调用 convertAndSend 方法: ``` @Autowired private RabbitTemplate rabbitTemplate; public void sendMessage(String message) { rabbitTemplate.convertAndSend("myexchange", "mykey", message); } ``` 4. 在需要接收消息的地方,注入 SimpleMessageListenerContainer 并实现 MessageListener 接口: ``` @Autowired private Queue queue; @Bean public SimpleMessageListenerContainer container(ConnectionFactory connectionFactory, MessageListenerAdapter listenerAdapter) { SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(); container.setConnectionFactory(connectionFactory); container.setQueueNames(queue.getName()); container.setMessageListener(listenerAdapter); return container; } @Bean public MessageListenerAdapter listenerAdapter() { return new MessageListenerAdapter(new MyMessageListener()); } public class MyMessageListener implements MessageListener { @Override public void onMessage(Message message) { String body = new String(message.getBody()); System.out.println("Received message: " + body); } } ``` 通过以上步骤,我们就可以实现交换机和队列的绑定,以及在队列中发送和接收消息

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

i余数

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值