消息可靠性

-
消息从生产者发送到Exchange,再到Queue,再到消费者,有三种消息丢失的可能性:
- 生产者发送消息时丢失:
- 生产者发送消息时连接MQ失败
- 生产者发送的消息到达MQ后未送达Exchange
- 生产者发送消息到达MQ的
Exchange后,未找到合适的Queue
- MQ宕机导致Queue消息丢失
- 消息到达MQ并保存到队列后,尚未消费就突然宕机
- 消费者处理消息时:
- 消费者接收到消息后未消费就宕机
- 消费者接收到消息,在处理过程中抛出异常
- 生产者发送消息时丢失:
-
为保证MQ的(消息)可靠性,解决以上三种消息丢失问题,就需要保证消息传递的每个步骤的可靠性即可,主要从三方面解决:确保生产者一定把消息发送到MQ、确保MQ不会将消息弄丢、确保消费者一定要处理消息
- 因此,为保证消息可靠性,需学习四个方面:
- 生产者消息确认
- 数据持久化
- 消费者消息确认
- 消费失败重试机制
- 因此,为保证消息可靠性,需学习四个方面:
-
注意
- 生产者消息确认、数据持久化、消费者消息确认三节内容的代码示例均以DirectExchange(路由交换机))示例为基础进行演示 ,系统中交换机(cgrs572.direct)、队列(direct.queue1和direct.queue2)均是已存在状态。因此,若不存在,则需先创建。具体内容可详见RabbitMQ基础部分内容
生产者消息确认

- 生产者确认机制
- RabbitMQ提供了publisher confirm机制来避免消息发送到MQ过程中丢失。
- 消息发送到MQ以后,会返回一个结果给发送者,表示消息是否处理成功。
- 生产者确认机制 结果有两种回执:
- publisher-confirm:发送确认回执
- 临时消息成功投递到交换机,则返回 publisher-confirm ACK确认信息
- 持久消息成功投递到交换机,则返回 publisher-confirm ACK确认信息
- 消息(临时消息、持久消息)未成功投递到交换机,返回 publisher-confirm NACK确认信息
- 返回确认信息(发送确认回执)用的是
ConfirmCallback - 注意:
- 只要成功投递到交换机,不论后续是什么样的流程(失败or成功),则一定会返回publisher-confirm ACK确认信息;只要投递到交换机失败,则一定会返回publisher-confirm NACK确认信息
- publisher-confirm(发送确认回执) 只关心是否成功投递到交换机。至于成功投递到交换机后,是否路由到Queue不是其关心的问题
- publisher-return:发送者回执
- 消息投递到交换机(前提),但没有路由到Queue(关键) (即路由失败,未入队成功),则返回 publisher-confirm ACK确认信息 及 publisher-return 路由失败原因(
mandatory=true) - 消息投递到交换机(前提),并成功路由到Queue(关键) (即入队成功),此时仅返回 publisher-confirm ACK确认信息 , publisher-return不会返回任何信息
- 返回路由失败原因(发送者回执)用的是
ReturnCallback - 注意:
- 消息未成功投递到交换机,此时只会返回publisher-confirm NACK确认信息,并不会触发publisher-return,因为此时消息根本就没有到路由这一步
- 消息投递到交换机(前提),但没有路由到Queue(关键) (即路由失败,未入队成功),则返回 publisher-confirm ACK确认信息 及 publisher-return 路由失败原因(
- publisher-confirm:发送确认回执
SpringAMQP生产者消息确认示例
本示例以 路由交换机示例 为基础进行演示,博主系统中交换机(cgrs572.direct)、队列(direct.queue1和direct.queue2)均是已存在状态。因此,若不存在,则需先创建。具体内容可详见RabbitMQ基础部分内容
前置步骤
-
在生产者模块(即publisher模块)的application.yml文件中新增配置,代码如下
yamlspring: rabbitmq: publisher-confirm-type: correlated # 开启publisher confirm机制,并设置confirm类型 publisher-returns: true # 开启publisher-return机制,同样是基于callback机制,不过定义的是ReturnCallback template: # 定义消息路由失败时所采取的策略 mandatory: true # true:调用ReturnCallback false:直接丢弃消息配置文件完整代码如下:
yamllogging: pattern: dateformat: MM-dd HH:mm:ss:SSS level: at.guigu: debug spring: rabbitmq: host: 192.168.204.128 # rabbitMQ的公司ip地址 (自用地址192.168.10.100) port: 5672 # 配置RabbitMQ的消息发送处理接口(即消息通信接口) username: root # 配置RabbitMQ的用户名 password: 123456 # 配置RabbitMQ的密码 virtual-host: / # 配置可使用的虚拟主机 publisher-confirm-type: correlated # 开启publisher confirm机制,并设置confirm类型 publisher-returns: true # 开启publisher-return机制,同样是基于callback机制,不过定义的是ReturnCallback template: # 定义消息路由失败时所采取的策略 mandatory: true # true:调用ReturnCallback false:直接丢弃消息publisher-confirm-type:开启publisher confirm机制,并设置confirm类型,有两种类型simple(不推荐):生产者发送消息后,会同步等待MQ的confirm结果(即回执),直到超时。因此,这种方式又叫做同步阻塞等待,会导致代码的阻塞correlated(推荐):MQ异步回调。也就是说生产者发送消息后不会同步等待,而是会去做其它事情,返回MQ的confirm结果(即回执)会通过callback机制(即回调机制)来完成,即定义ConfirmCallback,MQ返回结果时会回调ConfirmCallback
publisher-returns:开启publisher-return功能,同样是基于callback机制(即回调机制),不过定义的是ReturnCallback,MQ返回结果时会回调ReturnCallbacktemplate.mandatory:定义消息路由失败时的策略true:路由失败时会通过callback机制(即回调机制)来返回路由失败原因,回调的是ReturnCallbackfalse:直接丢弃消息,并不会通过callback机制(即回调机制)返回路由失败原因
配置回调函数ReturnCallback
目标:配置publisher-return的回调机制所需的回调函数
注意:每个
RabbitTemplate只能配置一个ReturnCallback,也就是在配置类中统一设置(此时属于全局配置,都会共用这一个)
解释:
RabbitTemplate是由Spring创建的单例Bean,并不是由我们自己创建的,所以RabbitTemplate只有一个。- 又因为
RabbitTemplate只能配置一个ReturnCallback,所以我们不能在每次发消息时配置(因为在每次发消息时配置的话就会配置多个ReturnCallback)- 所以需要在项目启动时配置,使其全局生效,也就是在配置类中统一配置
方式一
- 利用
ApplicationContextAware接口。它是一种 "反向注入机制" ,
允许一个 Bean 在初始化时获得 Spring 容器的引用(即ApplicationContext),从而能主动获取其他 Bean 或资源。由此来提前获取RabbitTemplate并配置一个ReturnCallback- 此处通过创建一个实现
ApplicationContextAware接口的配置类,并重写其中的setApplicationContext方法来获取到Spring 容器的引用(即ApplicationContext),并通过容器获取RabbitTemplate,进而配置ReturnCallback,以此来实现全局使用一个ReturnCallback方式二
- 利用
@PostConstruct注解。它是一个生命周期回调注解,用在方法上,让Bean在 创建并完成依赖注入之后、正式被使用之前(对外可用之前) 自动执行一次- 由此来实现
RabbitTemplate被正式使用前配置一个ReturnCallback注意:以上两种方式是基于对Spring生命周期的理解,可详见Spring完整知识点一中SPring的生命周期
-
方式一: 在生产者模块(即publisher)中创建config包,并在该包下创建实现了
ApplicationContextAware接口的配置类CommonConfig,代码如下:-
SpringBoot2.6.x版本之前,使用
ReturnCallback接口该版本案例Demo已上传至Gitee,可详见Gitee项目messageConfirm/publisherConfirm/ReturnCallback-方式1/SpringBoot2.6.x之前
javapackage at.guigu.mq.config; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.annotation.Configuration; @Slf4j @Configuration public class CommonConfig implements ApplicationContextAware { @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { // 获取RabbitTemplate RabbitTemplate rabbitTemplate = applicationContext.getBean(RabbitTemplate.class); // 设置ReturnsCallback rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> { log.info("消息发送失败,应答码{},原因{},交换机{},路由键{},消息{}",replyCode, replyText, exchange, routingKey, message.toString()); }); } } -
SpringBoot2.6.x版本及该版本之后,使用
ReturnsCallback接口该版本案例Demo已上传至Gitee,可详见Gitee项目messageConfirm/publisherConfirm/ReturnCallback-方式1/SpringBoot2.6.x之后
javapackage at.guigu.mq.config; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.core.Message; import org.springframework.amqp.core.ReturnedMessage; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.annotation.Configuration; @Slf4j @Configuration public class CommonConfig implements ApplicationContextAware { @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { // 获取RabbitTemplate RabbitTemplate rabbitTemplate = applicationContext.getBean(RabbitTemplate.class); // 设置ReturnsCallback rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback() { @Override public void returnedMessage(ReturnedMessage returnedMessage) { int replyCode = returnedMessage.getReplyCode(); String replyText = returnedMessage.getReplyText(); String exchange = returnedMessage.getExchange(); String routingKey = returnedMessage.getRoutingKey(); Message message = returnedMessage.getMessage(); log.info("消息发送失败,应答码{},原因{},交换机{},路由键{},消息{}",replyCode, replyText, exchange, routingKey, message.toString()); } }); /* Lambda表达式形式 rabbitTemplate.setReturnsCallback(returnedMessage -> { int replyCode = returnedMessage.getReplyCode(); String replyText = returnedMessage.getReplyText(); String exchange = returnedMessage.getExchange(); String routingKey = returnedMessage.getRoutingKey(); Message message = returnedMessage.getMessage(); log.info("消息发送失败,应答码{},原因{},交换机{},路由键{},消息{}",replyCode, replyText, exchange, routingKey, message.toString()); });*/ } }
-
-
方式二: 在生产者模块(即publisher)中创建config包,并在该包下创建配置类
CommonConfig,代码如下-
SpringBoot2.6.x版本之前,使用
ReturnCallback接口该版本案例Demo已上传至Gitee,可详见Gitee项目messageConfirm/publisherConfirm/ReturnCallback-方式2/SpringBoot2.6.x之前
javapackage at.guigu.mq.config; import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.context.annotation.Configuration; /** * SpringBoot2.6.x版本之前,使用`ReturnCallback`接口配置回调函数 * 由于博主使用的SpringBoot是3.x,因此才会报错 */ @Slf4j @Configuration @RequiredArgsConstructor public class CommonConfig { private final RabbitTemplate rabbitTemplate; @PostConstruct public void init() { // 设置ReturnCallback rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> { log.info("消息发送失败,应答码{},原因{},交换机{},路由键{},消息{}",replyCode, replyText, exchange, routingKey, message.toString()); }); } } -
SpringBoot2.6.x版本及该版本之后,使用
ReturnsCallback接口该版本案例Demo已上传至Gitee,可详见Gitee项目messageConfirm/publisherConfirm/ReturnCallback-方式2/SpringBoot2.6.x之后
javapackage at.guigu.mq.config; import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.core.Message; import org.springframework.amqp.core.ReturnedMessage; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.context.annotation.Configuration; /** * 方式二 * SpringBoot2.6.x版本之后,使用`ReturnsCallback`接口配置回调函数 */ @Slf4j @Configuration @RequiredArgsConstructor public class CommonConfig { private final RabbitTemplate rabbitTemplate; /** * SpringBoot2.6.x版本及该版本之后,使用`ReturnsCallback`接口 */ @PostConstruct public void init() { // 设置ReturnCallback rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback() { @Override public void returnedMessage(ReturnedMessage returnedMessage) { int replyCode = returnedMessage.getReplyCode(); String replyText = returnedMessage.getReplyText(); String exchange = returnedMessage.getExchange(); String routingKey = returnedMessage.getRoutingKey(); Message message = returnedMessage.getMessage(); log.error("消息发送失败,应答码{},原因{},交换机{},路由键{},消息{}",replyCode, replyText, exchange, routingKey, message.toString()); } }); /* Lambda表达式形式 rabbitTemplate.setReturnsCallback(returnedMessage -> { int replyCode = returnedMessage.getReplyCode(); String replyText = returnedMessage.getReplyText(); String exchange = returnedMessage.getExchange(); String routingKey = returnedMessage.getRoutingKey(); Message message = returnedMessage.getMessage(); log.error("消息发送失败,应答码{},原因{},交换机{},路由键{},消息{}",replyCode, replyText, exchange, routingKey, message.toString()); });*/ } }
-
配置回调函数ConfirmCallback
目标:配置publisher-confirm的的回调机制所需的回调函数
注意:
- 由于每个消息发送时的处理逻辑不一定相同,因此每个
RabbitTemplate可以针对不同的业务处理方案配置不同的ConfirmCallback,此时属于局部配置。- 配置回调函数
ConfirmCallback的示例在 配置回调函数ReturnCallback的基础上演示。因此,以案例DemomessageConfirm/publisherConfirm/ReturnCallback-方式2/SpringBoot2.6.x之后作为基础框架
-
原理:在SpringBoot新老版本的不同方式演示中,均调用了
RabbitTemplate类中的convertAndSend()方法,源码如下:javapublic void convertAndSend(String exchange, String routingKey, Object object, @Nullable CorrelationData correlationData) throws AmqpException { this.send(exchange, routingKey, this.convertMessageIfNecessary(object), correlationData); }convertAndSend()方法中有个参数CorrelationData,对应该类的源码如下-
SpringBoot3.x版本之前:
javapublic class CorrelationData implements Correlation { private final SettableListenableFuture<Confirm> future = new SettableListenableFuture(); private volatile String id; private volatile ReturnedMessage returnedMessage; ... } -
SpringBoot3.x版本之后:
javapublic class CorrelationData implements Correlation { private final CompletableFuture<Confirm> future = new CompletableFuture(); private volatile String id; private volatile ReturnedMessage returnedMessage; ... } -
从以上两个版本的
CorrelationData类中可看出均有两个核心参数:id:消息的唯一标识,MQ对不同的消息的回执以此做判断,避免混淆- 获取回执结果的Future对象:
- 老版本为
SettableListenableFuture - 新版本为
CompletableFuture
- 老版本为
-
未来MQ的回执就会通过这个
Future来返回,因此可以提前给CorrelationData中的Future接口的实现类添加回调函数来处理消息回执,所以在后续的代码实现步骤中,就需要去先获取到对应Future接口的实现类,然后在去配置回调函数
-
-
SpringBoot3.x版本之前方式及步骤如下:
该版本案例Demo已上传至Gitee,可详见Gitee项目messageConfirm/publisherConfirm/ConfirmCallback/SpringBoot3.x之前
-
Step1: 在生产者模块(即publisher)的
test包下创建测试类ConfirmCallbackTest,并在该类中设置消息体 -
Step2: 创建
CorrelationData对象,并将消息ID封装到CorrelationData对象中(存在消息Id的情况下) -
Step3:
CorrelationData对象添加callback- SpringBoot3.x版本之前,
CorrelationData的getFuture()返回的是一个ListenableFuture<CorrelationData.Confirm>,ListenableFuture有addCallback()方法,可以用来配置回调函数ConfirmCallback - 但是在SpringBoot3.x版本之后
CorrelationData的getFuture()返回的是一个CompletableFuture<CorrelationData.Confirm>,CompletableFuture没有addCallback()方法,所以此时就不能用addCallback()来配置回调函数ConfirmCallback
- SpringBoot3.x版本之前,
-
Step4: 利用
RabbitTemplate发送消息javapackage at.guigu.mq; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; import org.springframework.amqp.rabbit.connection.CorrelationData; import org.springframework.amqp.rabbit.core.RabbitTemplate; import java.util.UUID; /** * 配置回调函数`ConfirmCallback` * SpringBoot3.x版本之前的方式 * 注意:若使用的版本是SpringBoot3.x版本之后,则该代码会报错 */ @Slf4j @RequiredArgsConstructor public class ConfirmCallbackTest { private RabbitTemplate rabbitTemplate; @Test public void publisherConfirm() { // 设置消息体 String message = "Hello ConfirmCallback!"; // 将消息体对应的消息Id封装到CorrelationData中 CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString()); //添加callback correlationData.getFuture().addCallback( result -> { if (result.isAck()) { log.debug("消息发送成功, ID:{}", correlationData.getId()); } else { log.error("消息发送失败, ID:{}, 原因:{}", correlationData.getId(), result.getReason()); } }, ex -> log.error("消息发送异常, ID:{}, 原因:{}", correlationData.getId(), ex.getMessage()) ); /* 等同于 correlationData.getFuture().addCallback(new ListenableFutureCallback<CorrelationData.Confirm>() { @Override public void onSuccess(CorrelationData.Confirm result) { if (result.isAck()) { log.debug("消息发送成功, ID:{}", correlationData.getId()); } else { log.error("消息发送失败, ID:{}, 原因:{}", correlationData.getId(), result.getReason()); } } @Override public void onFailure(Throwable ex) { log.error("消息发送异常, ID:{}, 原因:{}", correlationData.getId(), ex.getMessage()); } });*/ // 发送消息 rabbitTemplate.convertAndSend("cgrs572.direct", "blue", message, correlationData); } }
-
-
SpringBoot3.x版本之后方式及步骤如下:
在SpringBoot3.x版本之后
CorrelationData的getFuture()返回的是一个CompletableFuture<CorrelationData.Confirm>,CompletableFuture没有addCallback()方法,所以此时就不能用addCallback()来配置回调函数ConfirmCallback。因此,此时可使用其它方式,博主只简单列举几种-
方式一:直接使用
RabbitTemplate的setConfirmCallback()方法来设置该版本案例Demo已上传至Gitee,可详见Gitee项目messageConfirm/publisherConfirm/ConfirmCallback/SpringBoot3.x之后/setConfirmCallback-方式一
注意: 创建的CorrelationData对象最好不要放在rabbitTemplate.setConfirmCallback之前,若要放在其之前,则必须保证创建的CorrelationData对象名与rabbitTemplate.setConfirmCallback的第一个参数名不一致(博主均命名为correlationData)原因:虽然在运行时是同一个对象,但在编译时他们是两个独立的变量声明,报错是编译阶段的语法规则(与运行时的对象关系无关)
javapackage at.guigu.mq; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; import org.springframework.amqp.AmqpException; import org.springframework.amqp.rabbit.connection.CorrelationData; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import java.util.Objects; import java.util.UUID; /** * SpringBoot3.x版本之后方式一 * 直接使用`RabbitTemplate`的`setConfirmCallback()`方法配置回调函数`ConfirmCallback` */ @Slf4j @SpringBootTest public class ConfirmCallbackTest { @Autowired private RabbitTemplate rabbitTemplate; @Test public void publisherConfirm() { rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> { if (Objects.isNull(correlationData)) { return; } if (ack) { log.debug("消息发送成功, ID:{}", correlationData.getId()); } else { log.error("消息发送失败, ID:{}, 原因:{}", correlationData.getId(), cause); } }); // 设置消息体 String message = "Hello ConfirmCallback!"; // 将消息体对应的消息Id封装到CorrelationData中 CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString()); // 发送消息 try { rabbitTemplate.convertAndSend("cgrs572.direct", "blue", message, correlationData); } catch (AmqpException ex) { log.error("消息发送失败, ID:{}, 原因:{}", correlationData.getId(), ex.getMessage()); } } } -
方式二:使用
CompletableFuture中的whenComplete()方法该版本案例Demo已上传至Gitee,可详见Gitee项目messageConfirm/publisherConfirm/ConfirmCallback/SpringBoot3.x之后/whenComplete-方式二
javapackage at.guigu.mq; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; import org.springframework.amqp.rabbit.connection.CorrelationData; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import java.util.Objects; import java.util.UUID; /** * SpringBoot3.x版本之后方式二 * 直接使用`CompletableFuture`中的`whenComplete()`方法配置回调函数`ConfirmCallback` */ @Slf4j @SpringBootTest public class ConfirmCallbackTest { @Autowired private RabbitTemplate rabbitTemplate; @Test public void publisherConfirm() { // 设置消息体 String message = "Hello ConfirmCallback!"; // 将消息体对应的消息Id封装到CorrelationData中 CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString()); // 使用`CompletableFuture`中的whenComplete()方法 correlationData.getFuture().whenComplete((result, ex) -> { if (Objects.nonNull(ex)) { log.error("消息发送异常, ID:{}, 原因:{}", correlationData.getId(), ex.getMessage()); } else if (result.isAck()) { log.debug("消息发送成功, ID:{}", correlationData.getId()); } else { log.error("消息发送失败, ID:{}, 原因:{}", correlationData.getId(), result.getReason()); } }); // 发送消息 rabbitTemplate.convertAndSend("cgrs572.direct", "blue", message, correlationData); } } -
方式三:使用
CompletableFuture中的thenAccept()方法该版本案例Demo已上传至Gitee,可详见Gitee项目messageConfirm/publisherConfirm/ConfirmCallback/SpringBoot3.x之后/thenAccept-方式三
javapackage at.guigu.mq; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; import org.springframework.amqp.rabbit.connection.CorrelationData; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import java.util.UUID; /** * SpringBoot3.x版本之后方式三 * 直接使用`CompletableFuture`中的`thenAccept()`方法配置回调函数`ConfirmCallback` */ @Slf4j @SpringBootTest public class ConfirmCallbackTest { @Autowired private RabbitTemplate rabbitTemplate; @Test public void publisherConfirm() { // 设置消息体 String message = "Hello ConfirmCallback!"; // 将消息体对应的消息Id封装到CorrelationData中 CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString()); // 使用 thenAccept() 处理成功情况,exceptionally() 处理异常 correlationData.getFuture() .thenAccept(result -> { if (result.isAck()) { log.debug("消息发送成功, ID:{}", correlationData.getId()); } else { log.error("消息发送失败, ID:{}, 原因:{}", correlationData.getId(), result.getReason()); } }) .exceptionally(ex -> { log.error("消息发送异常, ID:{}, 原因:{}", correlationData.getId(), ex.getMessage()); return null; }); // 发送消息 rabbitTemplate.convertAndSend("cgrs572.direct", "blue", message, correlationData); } }
-
-
注意:
-
在SpringBoot新老版本的不同方式演示中,均调用了
RabbitTemplate类中的convertAndSend()方法来发送消息javapublic void convertAndSend(String exchange, String routingKey, Object object, @Nullable CorrelationData correlationData) throws AmqpException { this.send(exchange, routingKey, this.convertMessageIfNecessary(object), correlationData); } -
convertAndSend()方法中有个参数CorrelationData,对应该类的源码如下javapublic class CorrelationData implements Correlation { private final CompletableFuture<Confirm> future = new CompletableFuture(); private volatile String id; private volatile ReturnedMessage returnedMessage; public CorrelationData() { this.id = UUID.randomUUID().toString(); } public CorrelationData(String id) { Assert.notNull(id, "'id' cannot be null and must be unique"); this.id = id; } ... }
-
测试类运行测试
-
当交换机名称、RoutingKey都无任何问题时,运行测试类发送成功

测试
ConfirmCallback
-
生产者发送消息时将交换机名称替换为错误名称,运行测试类,结果如图所示,可知成功触发回调函数
ReturnCallback,生产者发送失败
测试
ReturnCallback
-
生产者发送消息时将消息队列的名称替换为错误名称,运行测试类,结果如图所示,可知成功触发回调函数
ConfirmCallback。消息成功投递到交换机,但交换机路由失败
数据持久化
由于RabbitMQ默认是内存存储消息,若RabbitMQ宕机或重启就会导致未持久化的交换机以及队列被删除,进而导致消息丢失。因此,为保证数据的持久化存储,就需要实现交换机、队列以及消息的持久化存储
交换机及队列持久化
注解形式
注意:
本示例是在 生产者消息确认示例 基础上进行演示
本示例Demo可详见Gitee项目messagePersistence/exchange-queue-persistence/注解形式
java
package at.guigu.mq.listener;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* 消费者消费消息(包含队列创建、绑定及监听)
*/
@Component
public class SpringAmqpDirectExchangeListener {
/**
* 参数durable:是否持久化,默认为true
* @Queue的参数autoDelete:当最后一个消费者与当前队列断开连接时,是否自动删除队列
* @Exchange的参数autoDelete:当没有队列绑定到当前交换机绑定时,是否自动删除交换机
*/
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "direct.queue1", durable = "true", autoDelete = "false"), // 队列direct.queue1
exchange = @Exchange(name = "cgrs572.direct", type = ExchangeTypes.DIRECT, durable = "true", autoDelete = "false"), // 交换机名称、类型、持久化
key = {"red", "blue"} // BindingKey
))
public void listenDirectQueue1(String msg) {
System.out.println("消费者1接收到direct.queue1的消息:【" + msg + "】");
}
/**
* 参数durable:是否持久化,默认为true
* @Queue的参数autoDelete:当最后一个消费者与当前队列断开连接时,是否自动删除队列
* @Exchange的参数autoDelete:当没有队列绑定到当前交换机绑定时,是否自动删除交换机
*/
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "direct.queue2", durable = "true", autoDelete = "false"), // 队列direct.queue2
exchange = @Exchange(name = "cgrs572.direct", type = ExchangeTypes.DIRECT, durable = "true", autoDelete = "false"), // 交换机名称、类型、持久化
key = {"red", "yellow"} // BindingKey
))
public void listenDirectQueue2(String msg) {
System.out.println("消费者2接收到direct.queue2的消息:【" + msg + "】");
}
}
配置类形式
注意:
本示例是在生产者消息确认基础上进行演示
本示例Demo可详见Gitee项目messagePersistence/exchange-queue-persistence/配置类形式
-
修改消费者模块(即consumer模块)的config包下的配置类
DirectConfig中声明交换机的代码javapackage at.guigu.mq.config; import org.springframework.amqp.core.*; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class DirectConfig { /** * 声明DirectExchange交换机---cgrs572.direct * 参数name:指定Direct交换机名称 * 参数durable:是否持久化,默认为true * 参数autoDelete:当没有队列绑定到当前交换机绑定时,是否自动删除交换机 * @return 返回一个Direct Exchange交换机(路由机制) */ @Bean public DirectExchange directExchange(){ return new DirectExchange("cgrs572.direct", true, false); } /** * 声明消息队列1---direct.queue1 * 参数name:指定要创建的消息队列名称 * 参数durable:是否持久化,默认为true * @return 返回指定名称且持久化的消息队列 */ @Bean public Queue directQueue1(){ return QueueBuilder.durable("direct.queue1").build(); // 等同于 // return new Queue("direct.queue1", true); } /** * 声明消息队列2---direct.queue2 * 参数name:指定要创建的消息队列名称 * 参数durable:是否持久化,默认为true * @return 返回指定名称且持久化的消息队列 */ @Bean public Queue directQueue2(){ return QueueBuilder.durable("direct.queue2").build(); // 等同于 // return new Queue("direct.queue2", true); } // 批量绑定 @Bean public Declarables bindings() { return new Declarables( BindingBuilder.bind(directQueue1()).to(directExchange()).with("red"), BindingBuilder.bind(directQueue1()).to(directExchange()).with("blue"), BindingBuilder.bind(directQueue2()).to(directExchange()).with("red"), BindingBuilder.bind(directQueue2()).to(directExchange()).with("yellow") ); } }
消息持久化存储
注意:
- 本示例是在 交换机及队列持久化示例 基础上进行演示
- 由于消息持久化仅需在生产者发送消息时,对消息进行包装,不涉及消费者的内容。因此,此时示例仅选择注解形式一种来演示即可(原因:生产者发送消息的代码一致)
- 本示例Demo可详见Gitee项目messagePersistence/messagePersistentStorage
-
修改生产者模块(即publisher模块)的测试类中发送消息的代码如下
javapackage at.guigu.mq; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; import org.springframework.amqp.AmqpException; import org.springframework.amqp.core.Message; import org.springframework.amqp.core.MessageBuilder; import org.springframework.amqp.core.MessageDeliveryMode; import org.springframework.amqp.rabbit.connection.CorrelationData; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import java.nio.charset.StandardCharsets; import java.util.Objects; import java.util.UUID; /** * SpringBoot3.x版本之后方式一 * 直接使用`RabbitTemplate`的`setConfirmCallback()`方法配置回调函数`ConfirmCallback` */ @Slf4j @SpringBootTest public class ConfirmCallbackTest { @Autowired private RabbitTemplate rabbitTemplate; @Test public void publisherConfirm() { rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> { if (Objects.isNull(correlationData)) { return; } if (ack) { log.debug("消息发送成功, ID:{}", correlationData.getId()); } else { log.error("消息发送失败, ID:{}, 原因:{}", correlationData.getId(), cause); } }); // 设置消息体 String message = "Hello ConfirmCallback!"; // 消息持久化 Message msg = MessageBuilder.withBody(message.getBytes(StandardCharsets.UTF_8)) .setDeliveryMode(MessageDeliveryMode.PERSISTENT) // 设置消息的投递方式持久化 .build(); // 将消息体对应的消息Id封装到CorrelationData中 CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString()); // 发送消息 try { rabbitTemplate.convertAndSend("cgrs572.direct", "blue", msg, correlationData); } catch (AmqpException ex) { log.error("消息发送失败, ID:{}, 原因:{}", correlationData.getId(), ex.getMessage()); } } }
注意
- 交换机、队列以及消息在默认情况下就已经是持久化,所以不需要做额外设置
- 本小节说明数据持久化的方式是因为有些情况是不需要做持久化的,方便去关闭持久化
消费者消息确认
本示例是在 生产者消息确认示例 基础上进行演示
- RabbitMQ支持消费者确认机制,即:消费者处理完消息后可以向MQ发送ACK回执,MQ收到确认消息后才会删除对应消息
- SpringAMQP提供了三种消费者消息确认模式
- none:关闭ACK,此时MQ假定消费者获取消息后会成功处理,因此在消息投递后不论是否被成功处理,都会被立即删除
- 当消费者消息确认模式设置为
none,此时若消费者消费失败,则会导致消息丢失
- 当消费者消息确认模式设置为
- manual:手动ACK,需要在业务代码结束后,调用API发送ACK
- auto(默认):自动ACK,由Spring检测listener代码(即消费者的监听器类)是否出现异常,若无异常则返回ACK;反之,则抛出异常并返回NACK
- 底层使用的是AOP。由Spring自动完成
- 当消费者消息确认模式设置为
auto时,此时若消息处理失败,则消息会重新进入队列并再次发送给消费者,消费者则会重新消费该消息,以此循环往复,直至消息被消费成功- 因此,该模式下可能会进入死循环,带来不必要的压力。为解决该问题,可设置消费者的失败重试机制
- none:关闭ACK,此时MQ假定消费者获取消息后会成功处理,因此在消息投递后不论是否被成功处理,都会被立即删除
示例:消息消费失败后被被立即删除
本示例Demo可详见Gitee项目messageConfirm/consumerConfirm/MessageProcessingFailed/drop
-
Step1: 在消费者模块(即consumer模块)的application.yml配置文件中添加相关代码即可配置消费者消息确认模式。
yamlspring: rabbitmq: listener: simple: prefetch: 1 # 每次只能获取一条消息,处理完成并触发了ACK确认机制后,才能获取下一个消息 acknowledge-mode: none # none:关闭ACK manual:手动ACK auto:自动ACK-
配置文件完整代码如下
yamllogging: pattern: dateformat: MM-dd HH:mm:ss:SSS spring: rabbitmq: host: 192.168.204.128 # rabbitMQ的ip地址 port: 5672 # 配置RabbitMQ的消息发送处理接口(即消息通信接口) username: root # 配置RabbitMQ的用户名 password: 123456 # 配置RabbitMQ的密码 virtual-host: / # 配置可使用的虚拟主机 listener: simple: prefetch: 1 # 每次只能获取一条消息,处理完成并触发了ACK确认机制后,才能获取下一个消息 acknowledge-mode: none # none:关闭ACK manual:手动ACK auto:自动ACK(默认)
-
-
Step2: 修改消费者模块(即consumer模块)的监听器类
SpringAmqpDirectExchangeListener中的消费消息的代码来模拟消息处理失败,代码修改如下javapackage at.guigu.mq.listener; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component; /** * 消费者监听并消费消息 */ @Slf4j @Component public class SpringAmqpDirectExchangeListener { @RabbitListener(queues = "direct.queue1") public void listenDirectQueue1(String msg) { log.info("消费者1接收到direct.queue1的消息:【" + msg + "】"); System.out.println(1 / 0); // 模拟消费失败 log.info("消费者1成功处理direct.queue1的消息"); } @RabbitListener(queues = "direct.queue2") public void listenDirectQueue2(String msg) { log.info("消费者2接收到direct.queue2的消息:【" + msg + "】"); System.out.println(1 / 0); // 模拟消费失败 log.info("消费者2成功处理direct.queue2的消息"); } } -
Step3: 运行生产者模块(即publisher模块)发送消息的测试类,运行成功后可看到RabbitMQ客户端对应Queue成功接收到消息

-
Step4: 运行消费者模块(即consumer模块)的启动类,此时消费者在接收到消息消费时,由于
System.out.println(1 / 0);会导致消费失败,进而导致消息被立即删除-
消费者消费失败截图如下

-
此时RabbitMQ客户端对应Queue已将消息丢失

-
失败重试机制
本示例Demo可详见Gitee项目messageConfirm/consumerConfirm/MessageProcessingFailed/RetryOnFailure
- 当配置消费者消息确认模式为
auto时 ,若消息一直处理失败或抛出异常,则消费者会不断尝试消费该消息。为了避免死循环,我们可以利用Spring的retry机制,即在消费者处理失败或抛出异常时利用本地重试,而不是让消息无限制的重新回到消息队列- 注意:利用本地重试的方式就是配置失败重试机制
前置步骤
-
在消费者模块(即consumer模块)的application.yml配置文件中配置消费者消息确认模式为
auto,并设置失败重试机制yamlspring: rabbitmq: listener: simple: prefetch: 1 # 每次只能获取一条消息,处理完成并触发了ACK确认机制后,才能获取下一个消息 acknowledge-mode: auto # none:关闭ACK manual:手动ACK auto:自动ACK retry: enabled: true # 开启消费者失败重试机制(默认为false) initial-interval: 1000 # 初始的失败重试的间隔时间(ms)---消费者第一次消费失败后需要等待1s,然后再去重试 multiplier: 2 # 失败重试间隔时间的倍数 下次等待时长 = initial-interval * multiplier^(n-1) 第一次等待1s,第二次等待2s,第三次等待4s max-attempts: 2 # 最大重试次数 # 状态是否缺失:默认true缺失(即无状态),则每次重试都会创建一个新的consumer实例,不会保留之前的消费者状态和上下文信息,适合不需要维护消费状态的业务场景 # false不缺失(即有状态),每次重试都是同一个consumer实例,适合需要维护消费状态的场景。若业务中包含事务,则必须为false,目的是为了在重试时避免事务失效 stateless: true-
完整代码如下
yamllogging: pattern: dateformat: MM-dd HH:mm:ss:SSS spring: rabbitmq: host: 192.168.204.128 # rabbitMQ的ip地址 port: 5672 # 配置RabbitMQ的消息发送处理接口(即消息通信接口) username: root # 配置RabbitMQ的用户名 password: 123456 # 配置RabbitMQ的密码 virtual-host: / # 配置可使用的虚拟主机 listener: simple: prefetch: 1 # 每次只能获取一条消息,处理完成并触发了ACK确认机制后,才能获取下一个消息 acknowledge-mode: auto # none:关闭ACK manual:手动ACK auto:自动ACK(默认) retry: enabled: true # 开启消费者失败重试机制(默认为false) initial-interval: 1000 # 初始的失败重试的间隔时间(ms)---消费者第一次消费失败后需要等待1s,然后再去重试 multiplier: 2 # 失败重试间隔时间的倍数 下次等待时长 = initial-interval * multiplier^(n-1) 第一次等待1s,第二次等待2s,第三次等待4s max-attempts: 3 # 最大重试次数 # 状态是否缺失:默认true缺失(即无状态),则每次重试都会创建一个新的consumer实例,不会保留之前的消费者状态和上下文信息,适合不需要维护消费状态的业务场景 # false不缺失(即有状态),每次重试都是同一个consumer实例,适合需要维护消费状态的场景。若业务中包含事务,则必须为false,目的是为了在重试时避免事务失效 stateless: true -
stateless:状态是否缺失- 默认true缺失(即无状态),则每次重试都会创建一个新的consumer实例,不会保留之前的消费者状态和上下文信息,适合不需要维护消费状态的业务场景
- false不缺失(即有状态),每次重试都是同一个consumer实例,适合需要维护消费状态的场景。若业务中包含事务,则必须为false,目的是为了在重试时避免事务失效
-
-
注意:配置失败重试机制后,若达到最大重试次数时消息仍被消费失败,则消息就会被丢弃
消费者模块配置类形式步骤
注意:注解形式此处省略演示,因为即使是在注解形式中,也是给
SpringAmqpDirectExchangeListener中添加相关错误代码来模拟消息处理失败的情况
-
修改消费者模块(即consumer模块)的监听器类
SpringAmqpDirectExchangeListener消费消息的代码来模拟消息处理失败,代码修改如下- 注意:利用
log.info输出日志来输出消费者接收到消息,这样更能清除的验证所配置的失败重试机制
javapackage at.guigu.mq.listener; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component; /** * 消费者监听并消费消息 */ @Slf4j @Component public class SpringAmqpDirectExchangeListener { @RabbitListener(queues = "direct.queue1") public void listenDirectQueue1(String msg) { log.info("消费者1接收到direct.queue1的消息:【" + msg + "】"); System.out.println(1 / 0); // 模拟消费失败 log.info("消费者1成功处理direct.queue1的消息"); } @RabbitListener(queues = "direct.queue2") public void listenDirectQueue2(String msg) { log.info("消费者2接收到direct.queue2的消息:【" + msg + "】"); System.out.println(1 / 0); // 模拟消费失败 log.info("消费者2成功处理direct.queue2的消息"); } } - 注意:利用
运行演示
-
Step1: 运行生产者模块(即publisher模块)发送消息的测试类,运行成功后可看到RabbitMQ客户端对应Queue成功接收到消息

-
Step2: 运行消费者模块(即consumer模块)的启动类,此时消费者在接收到消息消费时,由于
System.out.println(1 / 0);会导致消费失败,此时由于消费者消息确认模式为auto,且配置了失败重试机制,所以会进行重试-
消费者消费失败截图如下,由截图可知重试时间间隔会随着重试次数的增加而增加,并且在达到最大重试次数仍消费失败时,消息就会被丢弃
- 注意:第一次消费消费时,若消费失败则本身就属于一次重试

-
达到最大重试次数仍消费失败,RabbitMQ客户端对应Queue就会将消息丢失

-
消息重试失败后的处理策略
本示例Demo可详见Gitee项目messageConfirm/consumerConfirm/MessageProcessingFailed/RetryFailureHandlingStrategy
-
在配置失败重试机制后,若达到最大重试次数时消息仍被消费失败,此时就会由
MessageRecover接口来对消息进行处理,有三个对应实现类对消息进行处理-
RejectAndDontRequeueRecoverer(失败重试机制默认):达到最大重试次数若消息仍被消费失败,则直接reject,丢弃消息 -
ImmediateRequeueMessageRecover:达到最大重试次数若消息仍被消费失败,则直接返回NACK,消息会重新入队 -
RepublishMessageRecoverer(推荐):达到最大重试次数若消息仍被消费失败,则会将消息投递到指定的交换机
-
前置公共步骤
-
在消费者模块(即consumer模块)的application.yml配置文件中配置消费者消息确认模式为
auto,并设置失败重试机制yamlspring: rabbitmq: listener: simple: prefetch: 1 # 每次只能获取一条消息,处理完成并触发了ACK确认机制后,才能获取下一个消息 acknowledge-mode: auto # none:关闭ACK manual:手动ACK auto:自动ACK retry: enabled: true # 开启消费者失败重试机制(默认为false) initial-interval: 1000 # 初始的失败重试的间隔时间(ms)---消费者第一次消费失败后需要等待1s,然后再去重试 multiplier: 2 # 失败重试间隔时间的倍数 下次等待时长 = initial-interval * multiplier^(n-1) 第一次等待1s,第二次等待2s,第三次等待4s max-attempts: 2 # 最大重试次数 # 状态是否缺失:默认true缺失(即无状态),则每次重试都会创建一个新的consumer实例,不会保留之前的消费者状态和上下文信息,适合不需要维护消费状态的业务场景 # false不缺失(即有状态),每次重试都是同一个consumer实例,适合需要维护消费状态的场景。若业务中包含事务,则必须为false,目的是为了在重试时避免事务失效 stateless: true-
完整代码示例如下
yamllogging: pattern: dateformat: MM-dd HH:mm:ss:SSS spring: rabbitmq: host: 192.168.204.128 # rabbitMQ的ip地址 port: 5672 # 配置RabbitMQ的消息发送处理接口(即消息通信接口) username: root # 配置RabbitMQ的用户名 password: 123456 # 配置RabbitMQ的密码 virtual-host: / # 配置可使用的虚拟主机 listener: simple: prefetch: 1 # 每次只能获取一条消息,处理完成并触发了ACK确认机制后,才能获取下一个消息 acknowledge-mode: auto # none:关闭ACK manual:手动ACK auto:自动ACK retry: enabled: true # 开启消费者失败重试机制(默认为false) initial-interval: 1000 # 初始的失败重试的间隔时间(ms)---消费者第一次消费失败后需要等待1s,然后再去重试 multiplier: 2 # 失败重试间隔时间的倍数 下次等待时长 = initial-interval * multiplier^(n-1) 第一次等待1s,第二次等待2s,第三次等待4s max-attempts: 3 # 最大重试次数 # 状态是否缺失:默认true缺失(即无状态),则每次重试都会创建一个新的consumer实例,不会保留之前的消费者状态和上下文信息,适合不需要维护消费状态的业务场景 # false不缺失(即有状态),每次重试都是同一个consumer实例,适合需要维护消费状态的场景。若业务中包含事务,则必须为false,目的是为了在重试时避免事务失效 stateless: true
-
消费者模块配置类形式步骤
-
Step1: 在消费者模块(即consumer模块)的
config包下创建配置类ErrorMsgConfig,-
Step1-1: 在该类中声明接收失败消息的交换机、队列、两者之间绑定关系的
Bean及所用到的BindingKey值 -
Step1-2: 在该类中将消息重试失败后的处理策略改为
RepublishMessageRecoverer- 注意:
RepublishMessageRecoverer对应Bean的声明也可放在启动类中,此处不再演示
- 注意:
javapackage at.guigu.mq.config; import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.BindingBuilder; import org.springframework.amqp.core.DirectExchange; import org.springframework.amqp.core.Queue; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.amqp.rabbit.retry.MessageRecoverer; import org.springframework.amqp.rabbit.retry.RepublishMessageRecoverer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class ErrorMsgConfig { /** * 声明接收失败消息的DirectExchange交换机---cgrs572error.direct * 参数name:指定Direct交换机名称 * @return 返回一个Direct Exchange交换机(路由机制) */ @Bean public DirectExchange errorMessageExchange() { return new DirectExchange("cgrs572error.direct"); } /** * 声明接收失败消息的消息队列---errordirect.queue * 参数name:指定要创建的消息队列名称 * @return 返回指定名称且持久化的消息队列 */ @Bean public Queue errorQueue() { return new Queue("errordirect.queue"); } /** * 绑定交换机与队列 */ @Bean public Binding errorBinding() { return BindingBuilder.bind(errorQueue()).to(errorMessageExchange()).with("error"); } @Bean public MessageRecoverer republishMessageRecoverer(RabbitTemplate rabbitTemplate) { return new RepublishMessageRecoverer(rabbitTemplate, "cgrs572error.direct", "error"); } } -
后置公共步骤
-
修改消费者模块(即consumer模块)的监听器类
SpringAmqpDirectExchangeListener消费消息的代码来模拟消息处理失败,代码修改如下- 注意:利用log.info输出日志来输出消费者接收到消息,这样更能清除的验证所配置的失败重试机制
javapackage at.guigu.mq.listener; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component; /** * 消费者监听并消费消息 */ @Slf4j @Component public class SpringAmqpDirectExchangeListener { @RabbitListener(queues = "direct.queue1") public void listenDirectQueue1(String msg) { log.info("消费者1接收到direct.queue1的消息:【" + msg + "】"); System.out.println(1 / 0); // 模拟消费失败 log.info("消费者1成功处理direct.queue1的消息"); } @RabbitListener(queues = "direct.queue2") public void listenDirectQueue2(String msg) { log.info("消费者2接收到direct.queue2的消息:【" + msg + "】"); System.out.println(1 / 0); // 模拟消费失败 log.info("消费者2成功处理direct.queue2的消息"); } }
运行演示
-
Step1: 运行消费者模块(即consumer模块)的启动类
-
在RabbitMQ客户端可看到对应交换机、队列均成功创建并绑定

-
-
Step2: 运行生产者模块(即publisher模块)发送消息的测试类,此时消费者控制台如下图所示,显示达到最大重试次数仍消费失败,则将消息投递到指定接收失败消息的交换机

-
再次进入RabbitMQ控制台可看到,接收消费失败消息的交换机已将消息发送到与其绑定的队列
errordirect.queue中,如图一所示。进入该队列可发现此时接收的不仅有消息,还包括消费失败时的栈信息,如图二所示

-