RabbitMQ消息确认机制之Confirm模式

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/BruceLiu_code/article/details/102773204

1.简介

在RabbitMQ中,消息确认主要有生产者发送确认和消费者接收确认:

生产者发送确认:指生产者发送消息后到RabbitMQ服务器,如果RabbitMQ服务器收到消息,则会给我们生产者一个应答,用于告诉生产者该条消息已经成功到达RabbitMQ服务器中。
在这里插入图片描述消费者接收确认:用于确认消费者是否成功消费了该条消息。

消息确认的实现方式主要有两种,一种是通过事务的方式(channel.txSelect()、channel.txCommit()、channel.txRollback()),另外一种是confirm确认机制。因为事务模式比较消耗性能,在实际工作中也用的不多,这里主要介绍通过confirm机制来实现消息的确认,保证消息的准确性。

2.生产者发送确认

在RabbitMQ中实现生产者发送确认的方法(本文使用springboot项目),主要有两点:

配置文件中配置消息发送确认

spring.rabbitmq.publisher-confirms = true

生产者实现 RabbitTemplate.ConfirmCallback接口,重写方法

confirm(CorrelationData correlationData, boolean isSendSuccess, String error)

当然也可以通过注入的方式自定义confirm listener.

@Component
public class CustomConfirmAndReturnCallback implements RabbitTemplate.ConfirmCallback{
 
    @Override
    public void confirm(CorrelationData correlationData, boolean isSendSuccess, String error) {
        .........
    }
}

3.消费者接收确认

为了保证消息从队列可靠地到达消费者,RabbitMQ提供消息确认机制(message acknowledgment)。确认模式主要分为下面三种:

AcknowledgeMode.NONE:不确认
AcknowledgeMode.AUTO:自动确认
AcknowledgeMode.MANUAL:手动确认

注意:在springboot项目中通过在配置文件中指定消息确认的模式,如下指定手动确认模式:

手动确认与自动确认的区别

自动确认:这种模式下,当发送者发送完消息之后,它会自动认为消费者已经成功接收到该条消息。这种方式效率较高,当时如果在发送过程中,如果网络中断或者连接断开,将会导致消息丢失。
手动确认:消费者成功消费完消息之后,会显式发回一个应答(ack信号),RabbitMQ只有成功接收到这个应答消息,才将消息从内存或磁盘中移除消息。这种方式效率较低点,但是能保证绝大部分的消息不会丢失,当然肯定还有一些小概率会发生消息丢失的情况。

手动确认主要使用的方法有下面几个:

public void basicAck(long deliveryTag, boolean multiple):

deliveryTag 表示该消息的index(long类型的数字);
multiple 表示是否批量(true:将一次性ack所有小于deliveryTag的消息);

如果成功消费消息,一般调用下面的代码用于确认消息成功处理完

channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);

手动拒绝消息:

public void basicNack(long deliveryTag, boolean multiple, boolean requeue):告诉服务器这个消息我拒绝接收,basicNack可以一次性拒绝多个消息。
  
deliveryTag: 表示该消息的index(long类型的数字);
multiple: 是否批量(true:将一次性拒绝所有小于deliveryTag的消息);
requeue:指定被拒绝的消息是否重新回到队列;

示例:

channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
public void basicReject(long deliveryTag, boolean requeue):也是用于拒绝消息,但是只能拒绝一条消息,不能同时拒绝多个消息。

deliveryTag: 表示该消息的index(long类型的数字);
requeue:指定被拒绝的消息是否重新回到队列;

4.示例

下面通过一个示例说明在RabbitMQ中实现发送确认和消费者确认的使用方法。

引入pom.xml依赖:

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

配置文件application.yml:

server:
  port: 6666
spring:
  application:
    name: mq-message-confirm2
  rabbitmq:
    host: 127.0.0.1
    virtual-host: /vhost
    username: guest
    password: guest
    port: 5672
    #消息发送确认回调
    publisher-confirms: true
    #指定消息确认模式为手动确认
    listener:
      simple:
        acknowledge-mode: manual
    #发送返回监听回调
    publisher-returns: true
这里需要注意两点:
    spring.rabbitmq.publisher-confirms = true
    spring.rabbitmq.listener.simple.acknowledge-mode = manual

自定义消息发送、返回回调监听类

package com.bruceliu.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.nio.charset.StandardCharsets;

/**
 * @author bruceliu
 * @create 2019-10-27 23:08
 * @description 自定义消息发送确认的回调
 * 实现接口:implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback
 * ConfirmCallback:只确认消息是否正确到达交换机中,不管是否到达交换机,该回调都会执行;
 * ReturnCallback:如果消息从交换机未正确到达队列中将会执行,正确到达则不执行;
 */
@Component
public class CustomConfirmAndReturnCallback implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback {

    private static final Logger logger = LoggerFactory.getLogger(CustomConfirmAndReturnCallback.class);

    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * PostConstruct: 用于在依赖关系注入完成之后需要执行的方法上,以执行任何初始化.
     */
    @PostConstruct
    public void init() {
        //指定 ConfirmCallback
        rabbitTemplate.setConfirmCallback(this);
        //指定 ReturnCallback
        rabbitTemplate.setReturnCallback(this);
    }

    /**
     * 消息从交换机成功到达队列,则returnedMessage方法不会执行;
     * 消息从交换机未能成功到达队列,则returnedMessage方法会执行;
     */
    @Override
    public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
        logger.info("returnedMessage回调方法>>>" + new String(message.getBody(), StandardCharsets.UTF_8) + ",replyCode:" + replyCode
                + ",replyText:" + replyText + ",exchange:" + exchange + ",routingKey:" + routingKey);
    }

    /**
     * 如果消息没有到达交换机,则该方法中isSendSuccess = false,error为错误信息;
     * 如果消息正确到达交换机,则该方法中isSendSuccess = true;
     */
    @Override
    public void confirm(CorrelationData correlationData, boolean isSendSuccess, String error) {
        logger.info("confirm回调方法>>>回调消息ID为: " + correlationData.getId());
        if (isSendSuccess) {
            logger.info("confirm回调方法>>>消息发送到交换机成功!");
        } else {
            logger.info("confirm回调方法>>>消息发送到交换机失败!,原因 : [{}]", error);
        }
    }
}

注意这里我同时也实现了RabbitTemplate.ReturnCallback返回回调接口,并且重写了returnedMessage()方法,返回回调主要指的是:如果消息从交换机未正确到达队列中将会执行,正确到达则不执行returnedMessage()。

RabbitMQ配置信息

package com.bruceliu.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

/**
 * @author bruceliu
 * @create 2019-10-27 23:14
 * @description  RabbitMQ配置信息,绑定交换器、队列、路由键设置
 * 说明:
 * 1. 声明Exchange交换器;
 * 2. 声明Queue队列;
 * 3. 绑定:BindingBuilder绑定队列到交换器,并设置路由键;
 */
@Component
public class RabbitMQConfig {

    private static final String EXCHANGE_NAME = "message_confirm_exchange";
    private static final String QUEUE_NAME = "message_confirm_queue";
    private static final String ROUTING_KEY = "user.#";

    @Bean
    private TopicExchange topicExchange() {
        return new TopicExchange(EXCHANGE_NAME);
    }

    @Bean
    private Queue queue() {
        return new Queue(QUEUE_NAME);
    }

    @Bean
    private Binding bindingDirect() {
        return BindingBuilder.bind(queue()).to(topicExchange()).with(ROUTING_KEY);
    }

}

发送者:这里发送了三条消息,如果三条消息中某条消息已经被拒绝过一次,那么触发basicNack()重新回到队列中,如果该消息再次被拒绝,那么消费者将会调用basicReject()直接拒绝该条消息,以后也不会再次接收该消息。

package com.bruceliu.producer;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.UUID;

/**
 * @author bruceliu
 * @create 2019-10-27 23:16
 * @description 生产者
 */
@Component
public class Producer {

    private static final Logger logger = LoggerFactory.getLogger(Producer.class);
    private static final String EXCHANGE_NAME = "message_confirm_exchange";
    private static final String ROUTING_KEY = "user.";

    @Autowired
    public RabbitTemplate rabbitTemplate;

    public void sendMessage() {
        for (int i = 1; i <= 3; i++) {
            CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
            logger.info("【Producer】发送的消费ID = {}", correlationData.getId());
            String msg = "hello confirm message" + i;
            logger.info("【Producer】发送的消息 = {}", msg);
            rabbitTemplate.convertAndSend(EXCHANGE_NAME, ROUTING_KEY, msg, correlationData);
        }
    }

}

消费者1:这里模拟了在处理消息的时候触发一个空指针异常,用于触发拒绝某个消息。

package com.bruceliu.consumer;

import com.rabbitmq.client.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.io.IOException;

/**
 * @author bruceliu
 * @create 2019-10-27 23:23
 * @description 消费者1
 */
@Component
public class Consumer01 {

    private static final Logger logger = LoggerFactory.getLogger(Consumer01.class);

    @RabbitListener(queues = "message_confirm_queue")
    public void receiveMessage01(String msg, Channel channel, Message message) throws IOException {
        try {
            // 这里模拟一个空指针异常,
            String string = null;
            string.length();

            logger.info("【Consumer01成功接收到消息】>>> {}", msg);
            // 确认收到消息,只确认当前消费者的一个消息收到
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        } catch (Exception e) {
            if (message.getMessageProperties().getRedelivered()) {
                logger.info("【Consumer01】消息已经回滚过,拒绝接收消息 : {}", msg);
                // 拒绝消息,并且不再重新进入队列
                //public void basicReject(long deliveryTag, boolean requeue)
                channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
            } else {
                logger.info("【Consumer01】消息即将返回队列重新处理 :{}", msg);
                //设置消息重新回到队列处理
                // requeue表示是否重新回到队列,true重新入队
                //public void basicNack(long deliveryTag, boolean multiple, boolean requeue)
                channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
            }
            e.printStackTrace();
        }
    }

}

消费者2:该消费者为正常消费消息。

package com.bruceliu.consumer;

import com.rabbitmq.client.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.io.IOException;

/**
 * @author bruceliu
 * @create 2019-10-27 23:24
 * @description 消费者2
 */
@Component
public class Consumer02 {
    private static final Logger logger = LoggerFactory.getLogger(Consumer02.class);

    @RabbitListener(queues = "message_confirm_queue")
    public void receiveMessage02(String msg, Channel channel, Message message) throws IOException {
        try {
            logger.info("【Consumer02成功接收到消息】>>> {}", msg);
            // 确认收到消息,只确认当前消费者的一个消息收到
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        } catch (Exception e) {
            if (message.getMessageProperties().getRedelivered()) {
                logger.info("【Consumer02】消息已经回滚过,拒绝接收消息 : {}", msg);
                // 拒绝消息,并且不再重新进入队列
                //public void basicReject(long deliveryTag, boolean requeue)
                channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
            } else {
                logger.info("【Consumer02】消息即将返回队列重新处理 :{}", msg);
                //设置消息重新回到队列处理
                // requeue表示是否重新回到队列,true重新入队
                //public void basicNack(long deliveryTag, boolean multiple, boolean requeue)
                channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
            }
            e.printStackTrace();
        }
    }

}

启动项目,查看运行结果(运行结果不固定)

2019-10-28 12:05:06.023  INFO 6672 --- [ntContainer#1-1] com.bruceliu.consumer.Consumer02         : 【Consumer02成功接收到消息】>>> hello confirm message2
2019-10-28 12:05:06.023  INFO 6672 --- [ntContainer#0-1] com.bruceliu.consumer.Consumer01         : 【Consumer01】消息即将返回队列重新处理 :hello confirm message1
java.lang.NullPointerException
	at com.bruceliu.consumer.Consumer01.receiveMessage01(Consumer01.java:27)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:497)
	at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:170)
	at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:120)
	at org.springframework.amqp.rabbit.listener.adapter.HandlerAdapter.invoke(HandlerAdapter.java:49)
	at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.invokeHandler(MessagingMessageListenerAdapter.java:190)
	at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.onMessage(MessagingMessageListenerAdapter.java:127)
	at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:1552)
	at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.actualInvokeListener(AbstractMessageListenerContainer.java:1478)
	at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer$$Lambda$429/509806761.invokeListener(Unknown Source)
	at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.invokeListener(AbstractMessageListenerContainer.java:1466)
	at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doExecuteListener(AbstractMessageListenerContainer.java:1461)
	at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.executeListener(AbstractMessageListenerContainer.java:1410)
	at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.doReceiveAndExecute(SimpleMessageListenerContainer.java:870)
	at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.receiveAndExecute(SimpleMessageListenerContainer.java:854)
	at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.access$1600(SimpleMessageListenerContainer.java:78)
	at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.mainLoop(SimpleMessageListenerContainer.java:1137)
	at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.run(SimpleMessageListenerContainer.java:1043)
	at java.lang.Thread.run(Thread.java:745)
2019-10-28 12:05:06.023 java.lang.NullPointerException
 INFO 6672 --- [ntContainer#0-1] com.bruceliu.consumer.Consumer01         : 【Consumer01】消息即将返回队列重新处理 :hello confirm message3
	at com.bruceliu.consumer.Consumer01.receiveMessage01(Consumer01.java:27)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:497)
	at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:170)
	at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:120)
	at org.springframework.amqp.rabbit.listener.adapter.HandlerAdapter.invoke(HandlerAdapter.java:49)
	at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.invokeHandler(MessagingMessageListenerAdapter.java:190)
	at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.onMessage(MessagingMessageListenerAdapter.java:127)
	at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:1552)
	at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.actualInvokeListener(AbstractMessageListenerContainer.java:1478)
	at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer$$Lambda$429/509806761.invokeListener(Unknown Source)
	at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.invokeListener(AbstractMessageListenerContainer.java:1466)
	at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doExecuteListener(AbstractMessageListenerContainer.java:1461)
	at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.executeListener(AbstractMessageListenerContainer.java:1410)
	at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.doReceiveAndExecute(SimpleMessageListenerContainer.java:870)
	at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.receiveAndExecute(SimpleMessageListenerContainer.java:854)
	at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.access$1600(SimpleMessageListenerContainer.java:78)
	at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.mainLoop(SimpleMessageListenerContainer.java:1137)
	at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.run(SimpleMessageListenerContainer.java:1043)
	at java.lang.Thread.run(Thread.java:745)
2019-10-28 12:05:06.039  INFO 6672 --- [ntContainer#1-1] com.bruceliu.consumer.Consumer02         : 【Consumer02成功接收到消息】>>> hello confirm message1
2019-10-28 12:05:06.039  INFO 6672 --- [ntContainer#0-1] com.bruceliu.consumer.Consumer01         : 【Consumer01】消息已经回滚过,拒绝接收消息 : hello confirm message3
java.lang.NullPointerException
	at com.bruceliu.consumer.Consumer01.receiveMessage01(Consumer01.java:27)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:497)
	at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:170)
	at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:120)
	at org.springframework.amqp.rabbit.listener.adapter.HandlerAdapter.invoke(HandlerAdapter.java:49)
	at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.invokeHandler(MessagingMessageListenerAdapter.java:190)
	at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.onMessage(MessagingMessageListenerAdapter.java:127)
	at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:1552)
	at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.actualInvokeListener(AbstractMessageListenerContainer.java:1478)
	at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer$$Lambda$429/509806761.invokeListener(Unknown Source)
	at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.invokeListener(AbstractMessageListenerContainer.java:1466)
	at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doExecuteListener(AbstractMessageListenerContainer.java:1461)
	at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.executeListener(AbstractMessageListenerContainer.java:1410)
	at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.doReceiveAndExecute(SimpleMessageListenerContainer.java:870)
	at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.receiveAndExecute(SimpleMessageListenerContainer.java:854)
	at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.access$1600(SimpleMessageListenerContainer.java:78)
	at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.mainLoop(SimpleMessageListenerContainer.java:1137)
	at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.run(SimpleMessageListenerContainer.java:1043)
	at java.lang.Thread.run(Thread.java:745)

由控制台结果可见,hello confirm message3这条消息第一次被consumer1拒绝了一次,执行basicNack重新回到队列,第二次又被判断为该条消息已经回滚过,调用basicReject方法又被拒绝并且禁止重新回到队列,这样该条消息将不会被消费者重新消费。

hello confirm message2这条消息成功被consumer2消费掉,hello confirm message3这条消息第一次也被 consumer1拒绝了,但是在重新回到队列之后,被consumer2成功消费了。

同时可以看到,三条消息都正确从发送者到达交换机,所以都执行了 confirm(CorrelationData correlationData, boolean isSendSuccess, String error)回调方法。

2019-10-28 12:05:06.023  INFO 19012 --- [ 127.0.0.1:5672] c.b.c.CustomConfirmAndReturnCallback     : confirm回调方法>>>回调消息ID为: b70413cb-c642-47ec-b8e2-a0e290c66de3
2019-10-28 12:05:06.023  INFO 19012 --- [ 127.0.0.1:5672] c.b.c.CustomConfirmAndReturnCallback     : confirm回调方法>>>消息发送到交换机成功!

2019-10-28 12:05:06.023  INFO 19012 --- [ 127.0.0.1:5672] c.b.c.CustomConfirmAndReturnCallback     : confirm回调方法>>>回调消息ID为: c9f6da2d-731b-4849-8cba-187a3d7f0ae4
2019-10-28 12:05:06.023  INFO 19012 --- [ 127.0.0.1:5672] c.b.c.CustomConfirmAndReturnCallback     : confirm回调方法>>>消息发送到交换机成功!

2019-10-28 12:05:06.023  INFO 19012 --- [ 127.0.0.1:5672] c.b.c.CustomConfirmAndReturnCallback     : confirm回调方法>>>回调消息ID为: 28a58376-78e3-4d5d-80da-81663152a292
2019-10-28 12:05:06.023  INFO 19012 --- [ 127.0.0.1:5672] c.b.c.CustomConfirmAndReturnCallback     : confirm回调方法>>>消息发送到交换机成功!
2019-10-28 12:05:06.023  INFO 19012 --- [       Thread-2] o.s.s.concurrent.ThreadPoolTaskExecutor  : Shutting down ExecutorService 'applicationTaskExecutor'

因为消息都成功从交换机正确到达队列中,所有监听的returnCallback的returnedMessage()方法并没有被执行。下面我们测试一下,假设指定一个binding key不匹配的。修改下面的路由键,让消息无法从交换机正确路由到队列上:
在这里插入图片描述
首先在RabbitMQ管理控制台将之前的user.#绑定键解除绑定:
在这里插入图片描述
重新启动项目,查看控制台日志:

2019-10-28 12:13:09.223  INFO 25984 --- [           main] com.bruceliu.producer.Producer           : 【Producer】发送的消费ID = def5a3bc-d8f9-4dfb-99b5-b57465185451
2019-10-28 12:13:09.223  INFO 25984 --- [           main] com.bruceliu.producer.Producer           : 【Producer】发送的消息 = hello confirm message1
2019-10-28 12:13:09.223  INFO 25984 --- [           main] o.s.a.r.c.CachingConnectionFactory       : Attempting to connect to: [127.0.0.1:5672]
2019-10-28 12:13:09.285  INFO 25984 --- [           main] o.s.a.r.c.CachingConnectionFactory       : Created new connection: rabbitConnectionFactory#50cf5a23:0/SimpleConnection@7c551ad4 [delegate=amqp://guest@127.0.0.1:5672/, localPort= 65013]
2019-10-28 12:13:09.316  INFO 25984 --- [           main] com.bruceliu.producer.Producer           : 【Producer】发送的消费ID = 71878808-41be-4933-853c-99e4339a8611
2019-10-28 12:13:09.316  INFO 25984 --- [           main] com.bruceliu.producer.Producer           : 【Producer】发送的消息 = hello confirm message2
2019-10-28 12:13:09.316  INFO 25984 --- [ 127.0.0.1:5672] c.b.c.CustomConfirmAndReturnCallback     : returnedMessage回调方法>>>hello confirm message1,replyCode:312,replyText:NO_ROUTE,exchange:message_confirm_exchange,routingKey:user456.#
2019-10-28 12:13:09.316  INFO 25984 --- [ 127.0.0.1:5672] c.b.c.CustomConfirmAndReturnCallback     : confirm回调方法>>>回调消息ID为: def5a3bc-d8f9-4dfb-99b5-b57465185451
2019-10-28 12:13:09.316  INFO 25984 --- [ 127.0.0.1:5672] c.b.c.CustomConfirmAndReturnCallback     : confirm回调方法>>>消息发送到交换机成功!
2019-10-28 12:13:09.316  INFO 25984 --- [           main] com.bruceliu.producer.Producer           : 【Producer】发送的消费ID = c7c26855-118c-4fb4-99da-2a5700a08776
2019-10-28 12:13:09.316  INFO 25984 --- [           main] com.bruceliu.producer.Producer           : 【Producer】发送的消息 = hello confirm message3
2019-10-28 12:13:09.316  INFO 25984 --- [ 127.0.0.1:5672] c.b.c.CustomConfirmAndReturnCallback     : returnedMessage回调方法>>>hello confirm message2,replyCode:312,replyText:NO_ROUTE,exchange:message_confirm_exchange,routingKey:user456.#
2019-10-28 12:13:09.316  INFO 25984 --- [ 127.0.0.1:5672] c.b.c.CustomConfirmAndReturnCallback     : confirm回调方法>>>回调消息ID为: 71878808-41be-4933-853c-99e4339a8611
2019-10-28 12:13:09.316  INFO 25984 --- [ 127.0.0.1:5672] c.b.c.CustomConfirmAndReturnCallback     : confirm回调方法>>>消息发送到交换机成功!
2019-10-28 12:13:09.316  INFO 25984 --- [ 127.0.0.1:5672] c.b.c.CustomConfirmAndReturnCallback     : returnedMessage回调方法>>>hello confirm message3,replyCode:312,replyText:NO_ROUTE,exchange:message_confirm_exchange,routingKey:user456.#
2019-10-28 12:13:09.316  INFO 25984 --- [ 127.0.0.1:5672] c.b.c.CustomConfirmAndReturnCallback     : confirm回调方法>>>回调消息ID为: c7c26855-118c-4fb4-99da-2a5700a08776
2019-10-28 12:13:09.316  INFO 25984 --- [ 127.0.0.1:5672] c.b.c.CustomConfirmAndReturnCallback     : confirm回调方法>>>消息发送到交换机成功!

下面我们测试一下消息从发送者未能正确到达交换机的情况,这里主要修改一个不存在的交换机名称,这样消息就不能正确到达消费者监听队列所在的交换机message_confirm_exchange,从而触发confirmCallback中发送失败的情况,error为错误原因。
在这里插入图片描述运行结果

2019-10-28 12:19:05.810  INFO 5092 --- [           main] com.bruceliu.producer.Producer           : 【Producer】发送的消费ID = c75f5631-7fc4-451f-a32a-72f8926666fa
2019-10-28 12:19:05.826  INFO 5092 --- [           main] com.bruceliu.producer.Producer           : 【Producer】发送的消息 = hello confirm message1
2019-10-28 12:19:05.826  INFO 5092 --- [           main] o.s.a.r.c.CachingConnectionFactory       : Attempting to connect to: [127.0.0.1:5672]
2019-10-28 12:19:05.857  INFO 5092 --- [           main] o.s.a.r.c.CachingConnectionFactory       : Created new connection: rabbitConnectionFactory#50cf5a23:0/SimpleConnection@7c551ad4 [delegate=amqp://guest@127.0.0.1:5672/, localPort= 65190]
2019-10-28 12:19:05.904  INFO 5092 --- [           main] com.bruceliu.producer.Producer           : 【Producer】发送的消费ID = fb4e1898-3a3b-4caa-8ef1-8d6093987c3d
2019-10-28 12:19:05.904  INFO 5092 --- [           main] com.bruceliu.producer.Producer           : 【Producer】发送的消息 = hello confirm message2
2019-10-28 12:19:05.904 ERROR 5092 --- [ 127.0.0.1:5672] o.s.a.r.c.CachingConnectionFactory       : Channel shutdown: channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no exchange 'message_confirm_exchange123456' in vhost '/', class-id=60, method-id=40)
2019-10-28 12:19:05.904  INFO 5092 --- [           main] com.bruceliu.producer.Producer           : 【Producer】发送的消费ID = 937f06c5-16a6-49a9-9fb0-83f0562cd96c
2019-10-28 12:19:05.904  INFO 5092 --- [           main] com.bruceliu.producer.Producer           : 【Producer】发送的消息 = hello confirm message3
2019-10-28 12:19:05.904  INFO 5092 --- [nectionFactory1] c.b.c.CustomConfirmAndReturnCallback     : confirm回调方法>>>回调消息ID为: c75f5631-7fc4-451f-a32a-72f8926666fa
2019-10-28 12:19:05.904  INFO 5092 --- [nectionFactory1] c.b.c.CustomConfirmAndReturnCallback     : confirm回调方法>>>消息发送到交换机失败!,原因 : [channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no exchange 'message_confirm_exchange123456' in vhost '/', class-id=60, method-id=40)]
2019-10-28 12:19:05.904 ERROR 5092 --- [ 127.0.0.1:5672] o.s.a.r.c.CachingConnectionFactory       : Channel shutdown: channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no exchange 'message_confirm_exchange123456' in vhost '/', class-id=60, method-id=40)
2019-10-28 12:19:05.904  INFO 5092 --- [nectionFactory1] c.b.c.CustomConfirmAndReturnCallback     : confirm回调方法>>>回调消息ID为: fb4e1898-3a3b-4caa-8ef1-8d6093987c3d
2019-10-28 12:19:05.904  INFO 5092 --- [nectionFactory1] c.b.c.CustomConfirmAndReturnCallback     : confirm回调方法>>>消息发送到交换机失败!,原因 : [channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no exchange 'message_confirm_exchange123456' in vhost '/', class-id=60, method-id=40)]
2019-10-28 12:19:05.904 ERROR 5092 --- [ 127.0.0.1:5672] o.s.a.r.c.CachingConnectionFactory       : Channel shutdown: channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no exchange 'message_confirm_exchange123456' in vhost '/', class-id=60, method-id=40)
2019-10-28 12:19:05.904  INFO 5092 --- [nectionFactory1] c.b.c.CustomConfirmAndReturnCallback     : confirm回调方法>>>回调消息ID为: 937f06c5-16a6-49a9-9fb0-83f0562cd96c
2019-10-28 12:19:05.904  INFO 5092 --- [nectionFactory1] c.b.c.CustomConfirmAndReturnCallback     : confirm回调方法>>>消息发送到交换机失败!,原因 : [channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no exchange 'message_confirm_exchange123456' in vhost '/', class-id=60, method-id=40)]

5.总结

本文主要介绍了RabbitMQ中生产者消息发送确认和消费者接收确认,同时也扩展了监听返回回调returnCallback,在实际项目中,一般都用手动确认的方式,再加上一些补偿措施,这样可以保证绝大部分的消息不会出现丢失的情况。

猜你喜欢

转载自blog.csdn.net/BruceLiu_code/article/details/102773204
今日推荐