RabbitMQ Confirm与Returns模式

文章目录

  • [RabbitMQ Confirm 与 Return 消息确认机制](#RabbitMQ Confirm 与 Return 消息确认机制)
    • 一、核心概念
      • [1.1 Confirm 回调](#1.1 Confirm 回调)
      • [1.2 Return 回调](#1.2 Return 回调)
      • [1.3 两者关系](#1.3 两者关系)
    • [二、YAML 配置](#二、YAML 配置)
    • [三、三种 RabbitTemplate 配置](#三、三种 RabbitTemplate 配置)
      • [3.1 配置类完整代码](#3.1 配置类完整代码)
      • [3.2 三种模式对比](#3.2 三种模式对比)
    • [四、Controller 注入](#四、Controller 注入)
      • [4.1 构造函数注入](#4.1 构造函数注入)
      • [4.2 注入说明](#4.2 注入说明)
    • 五、四种发送模式演示
      • [5.1 模式1:普通发送(无回调)](#5.1 模式1:普通发送(无回调))
      • [5.2 模式2:Confirm 回调](#5.2 模式2:Confirm 回调)
      • [5.3 模式3:Confirm + Return(正确路由)](#5.3 模式3:Confirm + Return(正确路由))
      • [5.4 模式4:Confirm + Return(错误路由)](#5.4 模式4:Confirm + Return(错误路由))
    • [六、CorrelationData 说明](#六、CorrelationData 说明)
      • [6.1 作用](#6.1 作用)
      • [6.2 correlationId 的用途](#6.2 correlationId 的用途)
      • [6.3 使用示例](#6.3 使用示例)
    • 七、流程说明
    • 八、注意事项
    • 九、总结

RabbitMQ Confirm 与 Return 消息确认机制

在 RabbitMQ 中,消息发送的可靠性 是生产环境的核心关注点。Spring AMQP 提供了两种回调机制:Confirm 回调Return 回调,分别用于确认消息是否到达交换机、以及消息是否能正确路由到队列。

本文通过完整代码演示,说明三种配置模式的差异和使用场景。


一、核心概念

1.1 Confirm 回调

Confirm 回调用于确认消息是否成功到达交换机(Exchange)。

场景 回调结果 说明
消息到达交换机 ack = true 交换机已接收消息
消息未到达交换机 ack = false 交换机不存在或连接失败

注意:Confirm 回调只关心消息是否到达交换机,不关心消息是否被路由到队列。

1.2 Return 回调

Return 回调用于确认消息是否能正确路由到队列(Queue)。

场景 触发条件
消息无法路由到任何队列 routingKey 不匹配、队列不存在等

前提 :必须设置 mandatory = true,否则消息会被直接丢弃。

1.3 两者关系

复制代码
生产者 → 交换机 → 队列
         ↑         ↑
      Confirm    Return
      (是否到达)  (是否路由)
  • Confirm:消息是否到达交换机
  • Return:消息是否能路由到队列
  • 两者独立,可单独或同时启用

二、YAML 配置

yaml 复制代码
spring:
  rabbitmq:
    host: 192.168.1.100
    port: 5672
    username: study
    password: study
    virtual-host: test-virtual
    publisher-confirm-type: correlated  # 启用 Confirm 回调
    publisher-returns: true             # 启用 Return 回调
配置项 说明
publisher-confirm-type correlated 启用异步 Confirm 回调
publisher-returns true 启用 Return 回调

三、三种 RabbitTemplate 配置

3.1 配置类完整代码

java 复制代码
@Configuration
public class RabbitTemplateConfig {

    /**
     * 模式1:无回调(默认)
     * 适用于不需要确认的场景
     */
    @Bean
    @Primary
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        return new RabbitTemplate(connectionFactory);
    }

    /**
     * 模式2:Confirm 回调
     * 消息是否到达交换机
     */
    @Bean("rabbitTemplateConfirm")
    public RabbitTemplate rabbitTemplateConfirm(ConnectionFactory connectionFactory) {
        RabbitTemplate template = new RabbitTemplate(connectionFactory);
        template.setConfirmCallback((correlationData, ack, cause) -> {
            String correlationId = correlationData != null ? correlationData.getId() : "null";
            if (ack) {
                System.out.println("[Confirm] 消息到达交换机, correlationId=" + correlationId);
            } else {
                System.out.println("[Confirm] 消息未到达交换机, correlationId=" + correlationId + ", cause=" + cause);
            }
        });
        return template;
    }

    /**
     * 模式3:Confirm + Return 回调
     * 消息是否到达交换机 + 是否能路由到队列
     */
    @Bean("rabbitTemplateConfirmReturn")
    public RabbitTemplate rabbitTemplateConfirmReturn(ConnectionFactory connectionFactory) {
        RabbitTemplate template = new RabbitTemplate(connectionFactory);
        template.setMandatory(true);  // 必须设置,否则 Return 不触发
        template.setConfirmCallback((correlationData, ack, cause) -> {
            String correlationId = correlationData != null ? correlationData.getId() : "null";
            if (ack) {
                System.out.println("[Confirm+Return] 消息到达交换机, correlationId=" + correlationId);
            } else {
                System.out.println("[Confirm+Return] 消息未到达交换机, correlationId=" + correlationId + ", cause=" + cause);
            }
        });
        template.setReturnsCallback(returned -> {
            System.out.println("[Return] 消息无法路由到队列"
                    + ", exchange=" + returned.getExchange()
                    + ", routingKey=" + returned.getRoutingKey()
                    + ", replyCode=" + returned.getReplyCode()
                    + ", replyText=" + returned.getReplyText()
                    + ", message=" + new String(returned.getMessage().getBody()));
        });
        return template;
    }
}

3.2 三种模式对比

模式 Bean 名称 Confirm 回调 Return 回调 适用场景
模式1 rabbitTemplate 不需要确认
模式2 rabbitTemplateConfirm 需要确认消息到达交换机
模式3 rabbitTemplateConfirmReturn 需要完整确认链路

四、Controller 注入

4.1 构造函数注入

java 复制代码
@RestController
@RequestMapping("/confirm-demo")
public class ConfirmDemoController {

    private final RabbitTemplate rabbitTemplate;
    private final RabbitTemplate rabbitTemplateConfirm;
    private final RabbitTemplate rabbitTemplateConfirmReturn;

    public ConfirmDemoController(RabbitTemplate rabbitTemplate,
                                 @Qualifier("rabbitTemplateConfirm") RabbitTemplate rabbitTemplateConfirm,
                                 @Qualifier("rabbitTemplateConfirmReturn") RabbitTemplate rabbitTemplateConfirmReturn) {
        this.rabbitTemplate = rabbitTemplate;
        this.rabbitTemplateConfirm = rabbitTemplateConfirm;
        this.rabbitTemplateConfirmReturn = rabbitTemplateConfirmReturn;
    }
}

4.2 注入说明

参数 注解 说明
rabbitTemplate 注入默认的 @Primary Bean
rabbitTemplateConfirm @Qualifier("rabbitTemplateConfirm") 指定注入 Confirm 回调的 Bean
rabbitTemplateConfirmReturn @Qualifier("rabbitTemplateConfirmReturn") 指定注入 Confirm+Return 回调的 Bean

为什么不需要 @Autowired Spring 4.3+ 如果类只有 1 个构造方法,会自动进行依赖注入。


五、四种发送模式演示

5.1 模式1:普通发送(无回调)

java 复制代码
@GetMapping("/basic")
public String basic() {
    CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
    rabbitTemplate.convertAndSend(
            RabbitMQConstants.EXCHANGE_NAME,
            RabbitMQConstants.ROUTING_KEY,
            "basic message",
            correlationData);
    return "已发送(basic),correlationId=" + correlationData.getId();
}

输出:无回调日志

5.2 模式2:Confirm 回调

java 复制代码
@GetMapping("/confirm")
public String confirm() {
    CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
    rabbitTemplateConfirm.convertAndSend(
            RabbitMQConstants.EXCHANGE_NAME,
            RabbitMQConstants.ROUTING_KEY,
            "confirm message",
            correlationData);
    return "已发送(confirm),correlationId=" + correlationData.getId();
}

输出

复制代码
[Confirm] 消息到达交换机, correlationId=550e8400-e29b-41d4-a716-446655440000

5.3 模式3:Confirm + Return(正确路由)

java 复制代码
@GetMapping("/confirm-return")
public String confirmReturn() {
    CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
    rabbitTemplateConfirmReturn.convertAndSend(
            RabbitMQConstants.EXCHANGE_NAME,
            RabbitMQConstants.ROUTING_KEY,
            "confirm-return message",
            correlationData);
    return "已发送(confirm-return),correlationId=" + correlationData.getId();
}

输出

复制代码
[Confirm+Return] 消息到达交换机, correlationId=550e8400-e29b-41d4-a716-446655440001

5.4 模式4:Confirm + Return(错误路由)

java 复制代码
@GetMapping("/confirm-return-fail")
public String confirmReturnFail() {
    CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
    rabbitTemplateConfirmReturn.convertAndSend(
            RabbitMQConstants.EXCHANGE_NAME,
            "wrong-routing-key",  // 错误的 routingKey
            "confirm-return-fail message",
            correlationData);
    return "已发送(confirm-return-fail),correlationId=" + correlationData.getId();
}

输出

复制代码
[Confirm+Return] 消息到达交换机, correlationId=550e8400-e29b-41d4-a716-446655440002
[Return] 消息无法路由到队列, exchange=my-exchange, routingKey=wrong-routing-key, replyCode=312, replyText=NO_ROUTE, message=confirm-return-fail message

六、CorrelationData 说明

6.1 作用

CorrelationData 用于关联请求和响应,将发送的消息与 Confirm 回调关联起来。

java 复制代码
CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
rabbitTemplate.convertAndSend(exchange, routingKey, message, correlationData);

6.2 correlationId 的用途

用途 说明
消息追踪 通过 correlationId 追踪消息的完整生命周期
日志关联 将发送日志和 Confirm 日志关联
业务关联 将消息与业务ID(如订单号)关联

6.3 使用示例

java 复制代码
// 生成唯一ID
String orderId = "ORDER-" + System.currentTimeMillis();
CorrelationData correlationData = new CorrelationData(orderId);

// 发送消息
rabbitTemplate.convertAndSend(exchange, routingKey, message, correlationData);

// 在 Confirm 回调中使用
template.setConfirmCallback((data, ack, cause) -> {
    String id = data.getId();  // 获取 orderId
    if (ack) {
        log.info("订单 {} 消息到达交换机", id);
    } else {
        log.error("订单 {} 消息发送失败: {}", id, cause);
    }
});

七、流程说明

  1. 生产者发送消息到交换机
  2. Confirm 回调触发:确认消息是否到达交换机
  3. 消息路由:交换机根据 routingKey 将消息路由到队列
  4. Return 回调触发(如果设置了 mandatory):消息无法路由时触发

八、注意事项

  1. mandatory 必须设置 :使用 Return 回调时,必须设置 template.setMandatory(true),否则消息会被丢弃而不触发 Return 回调。

  2. Confirm 回调是异步的publisher-confirm-type: correlated 表示异步确认,消息发送后不会阻塞等待确认。

  3. Return 回调在 Confirm 之后:Return 回调一定在 Confirm 回调之后触发,因为需要先确认消息到达交换机,才能判断是否能路由。

  4. 性能影响:Confirm 和 Return 回调会增加一定的性能开销,生产环境根据实际需求选择合适的模式。

  5. 异常处理:在回调方法中要做好异常处理,避免回调异常影响主流程。


九、总结

场景 推荐配置
不需要确认 模式1(无回调)
需要确认消息到达交换机 模式2(Confirm)
需要完整确认链路 模式3(Confirm + Return)
关键业务(订单、支付) 模式3 + 消息持久化
日志、监控等非关键业务 模式1

通过合理配置 Confirm 和 Return 回调,可以有效监控消息发送的可靠性,及时发现和处理消息丢失问题。

相关推荐
Jul1en_2 分钟前
【Java 脚手架】封装通用工具类-1
java·spring boot·分布式·spring·spring cloud
Linux运维技术栈20 分钟前
业务不停机、数据零丢失:Redis / RabbitMQ / Elasticsearch 三大核心中间件升级改造集群无缝平滑迁移
redis·elasticsearch·rabbitmq
ACP广源盛1392462567344 分钟前
Qwen3.8-Max 开源预期下@ACP#企业级终端硬件演进机遇与 PCIe 交换芯片落地分析
大数据·人工智能·分布式·单片机·嵌入式硬件
六点_dn1 小时前
RabbitMQ常见知识点总结
分布式·rabbitmq
会博通·代码搬运工1 小时前
会博通API对接实战:工程企业文档分布式采集系统的技术实现与Python SDK详解
开发语言·分布式·python·线性代数·矩阵·架构·电子档案合规
逐光老顽童1 小时前
第 2 章:彻底搞懂 K8s Pod——从 YAML 到调度全流程
分布式·云原生
逐光老顽童1 小时前
第 1 章:Kubernetes 核心概念总览——Pod、Deployment、Service 一次搞懂
分布式·云原生
tang777892 小时前
分布式爬虫优化指南:如何用代理IP把采集效率提升300%
分布式·爬虫·python·tcp/ip·分布式爬虫·爬虫代理·代理ip
学者猫头鹰16 小时前
分布式事务实战教程
java·分布式
笨蛋不要掉眼泪19 小时前
RabbitMQ消息队列:MQ的可靠性
分布式·rabbitmq·java-rabbitmq