Java研学-RabbitMQ(六)

一 生产者消息可靠性

保证 RabbitMQ 生产者可靠性的关键在于:通过自动重连机制(如连接工厂配置心跳检测、失败自动恢复)确保网络异常时生产者能快速恢复连接,同时结合Publisher Confirm 确认机制(异步回调 ConfirmCallback 实时反馈消息是否成功到达交换机),对确认失败的消息触发重试逻辑(如指数退避重试或存入补偿队列),最终形成"连接恢复-消息确认-失败重试"的闭环,避免因网络抖动或瞬时故障导致消息丢失。

二 生产者重连

1 介绍

生产者重连是指当消息生产者(如RabbitMQ客户端)因网络故障、服务重启或认证失败等导致与消息中间件(Broker)的连接断开时,自动触发重新建立连接并恢复消息发送的机制,其核心目的是通过容错设计避免因临时故障导致消息丢失,提升系统可靠性。

2 application.yaml -- publisher,加入生产者重试配置

java 复制代码
spring:
  rabbitmq:
    host: 192.168.44.128
    port: 5672
    virtual-host: /midhuang
    username: dahuang
    password: "dahuang66"
    
    # 生产者重试配置
    connection-timeout: 1s    # 连接超时时间
    template:
      retry:
        enabled: true         # 开启生产者重试
        initial-interval: 1000ms # 初始重试间隔
        multiplier: 1.0       # 等待时间倍数
        max-attempts: 3       # 最大重试次数
        
logging:
  level:
    cn.tj.consumer.listeners: DEBUG # 设置为 DEBUG 以查看详细日志

3 发送阻塞消息

此时停掉MQ服务,再发送消息,消息发送失败时会进行3次重试,需注意此时的重试是连接失败的重试,而不是消息发送抛出异常的重试。抛出异常的话不会开启重试。

java 复制代码
@SpringBootTest
class PublisherApplicationTests {
    // 注入 RabbitTemplate
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    @Test
    void testSendObject() {
        // 1. 创建测试消息(Map结构)
        Map<String, Object> msg = new HashMap<>(2);
        msg.put("name", "dahuang");
        msg.put("age", 18);

        // 2. 发送消息到指定队列(需确保 routingKey 与队列绑定)
        rabbitTemplate.convertAndSend("object.queue", msg);

        // 3. 可选:验证发送成功(通过日志或断言接收端消息)
        System.out.println("Sent message: " + msg);
    }
}

SpringAMQP的重试是阻塞式的重试,当前线程会被阻塞,因此rabbitTemplate.convertAndSend之后的代码需要一直等待。 可以考虑使用异步线程来执行发送消息的代码。

三 生产者确认 -- 生产者消息可靠性

1 介绍

生产者确认机制通过PublisherConfirmPublisherReturn两种机制实现,其核心目的是确保消息从生产者到Broker的可靠性传递。

场景描述 PublisherConfirm PublisherReturn 最终状态 关键说明
消息成功投递到交换机,且路由到队列(非持久化) ✅ ACK ❌ 不触发 投递成功 临时消息仅需到达队列即确认,不保证持久化。
消息成功投递到交换机,且路由到队列(持久化完成) ✅ ACK ❌ 不触发 投递成功 持久消息需等待磁盘写入完成才返回ACK,性能较低但可靠性更高。
消息到达交换机,但路由失败(如路由键错误、队列不存在) ✅ ACK ⚠️ 触发(返回错误) 投递到交换机成功,但路由失败 需通过ReturnCallback获取具体失败原因(如NO_ROUTE),可结合重试或补偿逻辑。
消息未到达交换机(如网络中断、Broker不可用) ❌ NACK ❌ 不触发 投递失败 需结合重试机制或降级处理,避免消息丢失。

2 application.yaml -- publisher,加入生产者确认机制配置publisher-confirm-type

模式 类型 工作原理 性能影响 适用场景 注意事项
none 关闭确认 不启用任何确认机制,消息发送后立即返回,不关心是否成功到达 Broker。 无额外开销 对消息可靠性要求极低的场景(如日志记录、非关键通知)。 消息丢失风险高,需确保业务能容忍数据丢失。
simple 同步阻塞确认 发送消息后,线程阻塞等待 Broker 返回确认结果(ACK/NACK),超时或失败抛出异常。 高延迟 对消息可靠性要求高,且能接受同步阻塞的场景(如金融交易、订单处理)。 1. 吞吐量低(每条消息需等待确认); 2. 超时时间需合理设置(避免长时间阻塞)。
correlated 异步回调确认 发送消息后立即返回,通过回调接口(ConfirmCallback)异步接收确认结果。 最低性能损耗 高并发、对延迟敏感的场景(如实时通知、事件驱动架构)。 1. 需实现回调逻辑处理 ACK/NACK; 2. 结合重试机制处理 NACK 情况。
java 复制代码
spring:
  rabbitmq:
    host: 192.168.44.128
    port: 5672
    virtual-host: /midhuang
    username: dahuang
    password: "dahuang66"
    connection-timeout: 1s    # 连接超时时间
    
    # 生产者确认机制配置
    publisher-confirm-type: correlated  # 异步回调确认模式
    publisher-returns: true             # 开启Return机制(捕获路由失败)

    template:
      mandatory: true                   # 强制触发ReturnCallback
      # 生产者重试配置(针对网络波动等临时性失败)
      retry:
        enabled: true                   # 开启重试
        initial-interval: 1000ms        # 首次重试间隔(1秒)
        multiplier: 1.0                 # 后续重试间隔倍数(1.0表示固定间隔)
        max-attempts: 3                 # 最大重试次数(实际重试次数=max-attempts-1)
logging:
  level:
    cn.tj.consumer.listeners: DEBUG # 设置为 DEBUG 以查看详细日志

3 MqConfirmConfig -- 编写回调函数

每个RabbitTemplate只能配置一个ReturnCallback,因此需要在项目启动过程中配置。在 Spring AMQP 中,ReturnCallback 是必需的,因为当消息因路由键不匹配或队列不存在而无法到达队列时,RabbitMQ 默认会静默丢弃消息;

通过配置mandatory: truepublisher-returns: true后,ReturnCallback能捕获这些路由失败事件,避免消息丢失,同时允许开发者实现补偿逻辑(如日志记录或重试),且由于 RabbitTemplate 是单例的,回调需在项目启动时统一配置以保证唯一性。

java 复制代码
// ApplicationContextAware 是 Spring 提供的接口,实现它能让 Bean 在初始化时
// 通过 setApplicationContext() 方法获取 Spring 容器的引用,
// 从而动态访问其他 Bean(如代码中通过容器获取 RabbitTemplate)或操作容器功能
// 避免硬编码依赖,实现解耦。
@Slf4j
@Configuration
public class MqConfirmConfig implements ApplicationContextAware {
    // ApplicationContext 是 Spring 框架的核心容器接口,负责管理 Bean 的生命周期、配置依赖注入、提供环境上下文及访问应用资源,本质是所有 Spring 组件的中央注册表和运行时环境。
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        RabbitTemplate rabbitTemplate = applicationContext.getBean(RabbitTemplate.class);
        
        // 配置 ReturnsCallback(处理路由失败)
        rabbitTemplate.setReturnsCallback(returned -> {
            log.error("消息路由失败!交换机: {}, 路由键: {}, 消息: {}, 错误码: {}, 原因: {}",
                returned.getExchange(), 
                returned.getRoutingKey(), 
                new String(returned.getMessage().getBody()),
                returned.getReplyCode(), 
                returned.getReplyText());
        });
    }
}

4 发送消息 -- PublisherApplicationTests

ConfirmCallback是 Spring AMQP 的异步回调接口,用于确认 RabbitMQ 消息是否成功到达交换机(ack 标识结果),需配合 publisher-confirm-type: correlated 配置,常与ReturnCallback联用保障消息发送可靠性。

java 复制代码
@Slf4j
@SpringBootTest
class PublisherApplicationTests {
    // 注入 RabbitTemplate
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    void testConfirmCallback() throws InterruptedException {
        // 1. 创建唯一消息标识(用于跟踪消息确认状态)
        CorrelationData cd = new CorrelationData(UUID.randomUUID().toString());
        // 2. 注册回调:处理消息确认结果(成功/失败)
        cd.getFuture().addCallback(new ListenableFutureCallback<CorrelationData.Confirm>() {
            @Override
            public void onFailure(Throwable ex) {
                // 2.1 回调异常时触发(如网络中断)
                log.error("消息回调失败", ex);
            }

            @Override
            public void onSuccess(CorrelationData.Confirm result) {
                // 2.2 收到RabbitMQ确认回执时触发
                log.debug("收到 ConfirmCallback 回执");
                if (result.isAck()) {
                    // 2.3 消息成功到达交换机
                    log.debug("消息发送成功,收到 ACK");
                } else {
                    // 2.4 消息被交换机拒绝(如交换机不存在)
                    log.error("消息发送失败,收到 NACK,原因: {}", result.getReason());
                }
            }
        });

        // 3. 发送消息到指定交换机和路由键(附带消息标识)
        rabbitTemplate.convertAndSend("dahuang.dircet", "yellow", "你的消息", cd);
        // 4. 休眠2秒接收回执,因为 RabbitMQ 的确认回调是异步的,
        // 不加 Thread.sleep(2000) 可能导致测试线程提前结束,未捕获到 ACK/NACK 回调结果。
        Thread.sleep(2000);
    }
}

5 发送错误消息

① 错误的交换机

java 复制代码
rabbitTemplate.convertAndSend("uang.direct", "yellow", "你的消息", cd);

② 错误的key

java 复制代码
rabbitTemplate.convertAndSend("huang.direct", "blue", "你的消息", cd);
相关推荐
缉毒英雄祁同伟2 分钟前
企业级WEB应用服务器TOMCAT
java·前端·tomcat
青云交35 分钟前
Java 大视界 -- 基于 Java 的大数据可视化在能源互联网全景展示与能源调度决策支持中的应用
java·大数据可视化·智能决策·能源互联网·三维渲染·能源调度·nsga-ii
盖世英雄酱5813637 分钟前
国企“高级”程序员写的那些问题代码(六期)
java·后端
藤椒鱼不爱编程39 分钟前
面向对象_类与对象
java
xcnwldgxxlhtff1 小时前
Java:线程池
java·开发语言
弹简特1 小时前
【Java web】HTTP 与 Web 基础教程
java·开发语言·前端
字节跳跃者1 小时前
Java 中的 Stream 可以替代 for 循环吗?
java·后端
北执南念2 小时前
如何在 Spring Boot 中设计和返回树形结构的组织和部门信息
java·spring boot·后端
遗憾皆是温柔2 小时前
19. 重载的方法能否根据返回值类型进行区分
java·开发语言·面试·学习方法
ts码农2 小时前
model层实现:
java·服务器·前端