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 回调,可以有效监控消息发送的可靠性,及时发现和处理消息丢失问题。

相关推荐
Mr__Miss2 小时前
Session、Cookie、JWT 详解:从单体到分布式的认证方案演进
分布式
Database_Cool_2 小时前
阿里云 PolarDB-X vs OceanBase 国产分布式数据库深度对比
分布式·阿里云·oceanbase
她说可以呀2 小时前
RabbitMQ重试机制
分布式·rabbitmq
wuqingshun3141593 小时前
RabbitMQ 中消息什么时候会进入死信交换机?
分布式·rabbitmq
hdsoft_huge3 小时前
SpringBoot系列12:整合RabbitMQ消息队列,异步解耦、死信队列完整业务案例
spring boot·rabbitmq·java-rabbitmq
ClickHouseDB5 小时前
推出 ClickHouse Cloud 中的多阶段分布式查询执行
分布式·clickhouse
BD_Marathon12 小时前
spark.sql报错
大数据·分布式·spark
长不胖的路人甲21 小时前
RabbitMQ 死信队列 DLQ
分布式·rabbitmq
可乐ea21 小时前
【Redis八股|第8篇】Redis 分布式锁原理与 Redisson 使用
数据库·redis·分布式·面试题·redis八股