你可知道publisherReturns参数在spring-boot-starter-amqp中的作用?

简介: 你可知道publisherReturns参数在spring-boot-starter-amqp中的作用?

众所周知,RabbitMQ在保证消息可靠投递的实现过程中有个参数mandatory。该参数的作用是,当消息的mandatory设置为true时,消息投递到Exchange之后,如果Exchange无法将该消息路由到任何一个队列,那么该消息将返回给生产者。当设置为false,RabbitMQ将直接丢弃该消息。


  • 在了解了这个背景之后,分为使用和不使用spring-boot-starter-amqp两种场景。
  • 在未使用spring-boot-starter-amqp的场景下,我们直接给channel设置监听器并且将消息的mandatory设置为true,即可实现消息无法路由之后通过该channel将消息return给生产者。即:
  1. channel设置监听回调函数。
  2. 消息的mandatory设置为true
  • 而在使用spring-boot-starter-amqp的场景下,除了设置mandatory,还需要设置spring.rabbitmq.publisher-returns,这个参数的作用是什么呢。并且在SpringBoot下尽管设置了mandatorytrue,但是同时spring.rabbitmq.publisher-returns为false,还是无法监听到路由失败return的消息。这是为什么?


演示过程:



定义一个Exchange,不绑定任何Queue,向该Exchange投递mandatorytrue消息,那么肯定是路由失败并且需要返回生产者。


# 不使用spring-boot-starter-amqp场景:

ConnectionFactory connectionFactory = new ConnectionFactory();
//connectionFactory.setUsername("futao");
//connectionFactory.setPassword("123456789");
connectionFactory.setVirtualHost("/tech-sharing");
//connectionFactory.setHost("localhost");
//connectionFactory.setPort(5672);
connectionFactory.setUri("amqp://futao:123456789@localhost:5672");
// 创建TCP连接
Connection connection = connectionFactory.newConnection();
// 创建通道
final Channel channel = connection.createChannel();
// 监听被return的消息
channel.addReturnListener(new ReturnCallback() {
    @Override
    public void handle(Return returnMessage) {
        log.info("消息被退回:{}", returnMessage);
    }
});
// 定义交换机
channel.exchangeDeclare("X_SIMPLE_NO_MATCH_QUEUE", BuiltinExchangeType.TOPIC, false, true, new HashMap<>(0));
AMQP.BasicProperties basicProperties = new AMQP.BasicProperties();
// 向Exchange投递mandatory为true的消息
channel.basicPublish("X_NO_MATCH_QUEUE", "", true, false, basicProperties, "123".getBytes(StandardCharsets.UTF_8));
TimeUnit.MINUTES.sleep(5L);
channel.close();
connection.close();


image.png


消息被投递到Exchange之后立即被退回并被我们的程序监听到。


  • 使用wireshark来查看这一过程。


image.png


接下来看看使用SpringBootStarter的方式。


# 使用spring-boot-starter-amqp:


  • 配置(注意spring.rabbitmq.publisher-returns):

spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=futao
spring.rabbitmq.password=123456789
spring.rabbitmq.virtual-host=/tech-sharing
# 当exchange无法找到任何一个合适的queue时,将消息return给生产者
spring.rabbitmq.template.mandatory=true
# 必须设置为true,否则消息消息路由失败也无法触发Return回调
spring.rabbitmq.publisher-returns=true
  • 设置回调函数

@Slf4j
@Component
public class RabbitTemplateEnhance implements BeanPostProcessor {
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof RabbitTemplate) {
            log.debug("增强 RabbitTemplate");
            RabbitTemplate rabbitTemplate = (RabbitTemplate) bean;
            // return回调函数
            rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback() {
                @Override
                public void returnedMessage(ReturnedMessage returnedMessage) {
                    log.error("消息被退回:{}", returnedMessage);
                }
            });
            return rabbitTemplate;
        }
        return bean;
    }
}
  • 发送消息

public void send() {
    log.info("发送消息");
    Order order = new Order(1, BigDecimal.TEN, OrderStatusEnum.UN_PAY.getStatus());
    Message message = MessageBuilder
            .withBody(JSON.toJSONString(order).getBytes(StandardCharsets.UTF_8))
            .setContentEncoding(StandardCharsets.UTF_8.displayName())
            .setContentType(MessageProperties.CONTENT_TYPE_JSON)
            .build();
    rabbitTemplate.convertAndSend(EXCHANGE_NAME, "", message);
}


如果将spring.rabbitmq.publisher-returns设置为flase进行测试,会惊奇的发现并不会触发回调方法。那么排查过程是首先是看RabbitMQ到底有没有通知生产者有消息被退回。使用wireshark抓包发现有一条Basic.Return报文,消息确实被退回了,那么我们的程序为什么没有触发回调函数呢?


Debug:


还是回到不使用SpringBoot的代码,在回调函数出打断点查看调用链。


image.png

监听器是被ChannelIN.processAsync()方法触发的。


image.png

ChannelIN.processAsync()中遍历了所有的监听器。那么查看监听器是如何被加入returnListeners集合的即可。

image.png

image.png

接下来分析为什么将spring.rabbitmq.publisher-returns设置为flase的场景下我们的监听器为什么没有被加入集合。


在添加回调监听器的地方打上断点


image.png

回到SpringBoot环境下Debug: 分析监听器是如何被加入到集合的。


image.png


发现这段代码会根据confirmsOrReturnsCapable的值判断是否需要向Channel添加监听器,而confirmsOrReturnsCapable的值来自于RabbitPropertiesisPublisherReturns


image.png

image.png

image.png

即:只有将spring.rabbitmq.publisher-returns设置为true,才会向Channel添加我们设置的监听回调函数。


spring.rabbitmq.publisher-returns参数的作用分析完了,那么mandatory属性是如何被设置到Message的呢:

通过rabbitTemplate.convertAndSend()一路往下debug会发现mandatory的取值过程:

image.png

bool mandatory = (RabbitTemplate.this.returnsCallback != null || (correlationData != null && StringUtils.hasText(correlationData.getId()))) && isMandatoryFor(message)

  • RabbitTemplate.this.returnsCallback != null我们在第二步设置了ReturnsCallback,所以为true
  • 那么就取决于isMandatoryFor(message)这个方法的返回值
  • isMandatoryFor(message)的值实际上来自:


image.png


如果mandatory != null,则取mandatory,否则取PublisherReturns的值。

那么是不是意味着如果将设置mandatorynullpublisherReturns的作用可以完全替代mandatory?

# 当exchange无法找到任何一个合适的queue时,将消息return给生产者
spring.rabbitmq.template.mandatory=
# 必须设置为true,否则消息消息路由失败也无法触发Return回调
spring.rabbitmq.publisher-returns=true
  • 经过测试发现,确实是这样。如果将spring.rabbitmq.template.mandatory设置为null,那么mandatory的效果可以完全被spring.rabbitmq.publisher-returns来控制。


# 总结



SpringBootStarter环境下要触发路由失败消息的回调可以有两种设置方式:

spring.rabbitmq.template.mandatory=true
spring.rabbitmq.publisher-returns=true

OR

spring.rabbitmq.template.mandatory=
spring.rabbitmq.publisher-returns=true


SpringBoot为什么要加这个参数???

相关实践学习
快速体验阿里云云消息队列RocketMQ版
本实验将带您快速体验使用云消息队列RocketMQ版Serverless系列实例进行获取接入点、创建Topic、创建订阅组、收发消息、查看消息轨迹和仪表盘。
消息队列 MNS 入门课程
1、消息队列MNS简介 本节课介绍消息队列的MNS的基础概念 2、消息队列MNS特性 本节课介绍消息队列的MNS的主要特性 3、MNS的最佳实践及场景应用 本节课介绍消息队列的MNS的最佳实践及场景应用案例 4、手把手系列:消息队列MNS实操讲 本节课介绍消息队列的MNS的实际操作演示 5、动手实验:基于MNS,0基础轻松构建 Web Client 本节课带您一起基于MNS,0基础轻松构建 Web Client
相关文章
|
消息中间件 安全 Java
【RabbitMQ高级篇】消息可靠性问题
【RabbitMQ高级篇】消息可靠性问题
404 0
|
iOS开发 MacOS Windows
解决报错 unable to access jarfile apachejmeter.jar 的一些技巧!🔥
解决报错 unable to access jarfile apachejmeter.jar 的一些技巧!🔥
2572 6
|
搜索推荐 安全 数据安全/隐私保护
构建高效网站后台会员管理系统:实战指南与代码示例
【7月更文挑战第5天】在当今的互联网时代,几乎每个网站或应用程序都需要一个强大的会员管理系统来维护用户信息、权限控制以及个性化体验。一个设计良好的会员管理系统不仅能够提升用户体验,还能增强数据安全性和运营效率。本文将深入探讨如何从零开始构建一个网站后台会员管理系统,涵盖系统设计思路、关键技术选型、功能模块实现,以及实战代码示例。
1339 3
|
存储 分布式计算 Hadoop
Hadoop Distributed File System (HDFS): 概念、功能点及实战
【6月更文挑战第12天】Hadoop Distributed File System (HDFS) 是 Hadoop 生态系统中的核心组件之一。它设计用于在大规模集群环境中存储和管理海量数据,提供高吞吐量的数据访问和容错能力。
1266 4
|
消息中间件 存储 监控
RabbitMQ 队列之战:Classic 和 Quorum 的性能洞察
RabbitMQ 是一个功能强大的消息代理,用于分布式应用程序间的通信。它通过队列临时存储消息,支持异步通信和解耦。经典队列适合高吞吐量和低延迟场景,而仲裁队列则提供高可用性和容错能力,适用于关键任务系统。选择哪种队列取决于性能、持久性和容错性的需求。
867 6
|
存储 JSON API
Python编程:解析HTTP请求返回的JSON数据
使用Python处理HTTP请求和解析JSON数据既直接又高效。`requests`库的简洁性和强大功能使得发送请求、接收和解析响应变得异常简单。以上步骤和示例提供了一个基础的框架,可以根据你的具体需求进行调整和扩展。通过合适的异常处理,你的代码将更加健壮和可靠,为用户提供更加流畅的体验。
772 0
|
SQL 存储 缓存
Mysql优化高级篇(全)
目录前言1. 简介1.1 安装1.2 MySQL逻辑架构存储引擎2. 索引优化分析2.1 原因2.2 常见通用的join查询2.3 索引2.3.1 索引分类2.3.2 索引结构2.3.3 索引情况2.4 性能分析2.4.1 id 前言 本篇文章主要涉及mysql的高级篇,主要是mysql的架构介绍、索引优化分析、查询截取分析、mysql锁机制以及主从复制等 在这之前的学习可参考我之前的文章进行学习 数据库知识 链接 数据库查询常用语句语法 博客链接 数据库中增删改常用语法语句(全) 博客
78839 19
Mysql优化高级篇(全)
|
消息中间件 存储 Java
【RabbitMQ教程】第七章 —— RabbitMQ - 发布确认高级
【RabbitMQ教程】第七章 —— RabbitMQ - 发布确认高级
209 0
|
算法 Java 开发者
Java:求正整数分解为质因数Java
Java:求正整数分解为质因数Java