从零起步学习RabbitMQ || 第三章:RabbitMQ的生产者、Broker、消费者如何保证消息不丢失(可靠性)详解

一、生产者可靠性:确保消息 "发得出、送得到"

生产者可靠性核心解决两个问题:网络 / 服务异常时的重连确认消息确实被 MQ 接收

1. 生产者重连(自动重连机制)

原理

RabbitMQ 客户端(Java 的 AMQP 客户端 / Spring AMQP)与 MQ 服务端建立 TCP 连接后,若因网络波动、MQ 宕机、连接超时等原因导致连接断开,客户端会通过 "连接状态监听 + 重连策略" 自动尝试重建连接,避免生产者因单次连接失败而永久无法发消息。

工作流程

关键配置

2. 生产者确认(Publisher Confirm)

原理

生产者发送消息后,MQ 会给生产者返回 "确认回执"(Confirm),生产者只有收到回执,才确认消息已被 MQ 接收;若未收到回执,说明消息可能丢失,需重试。RabbitMQ 提供三种确认模式:

  • 普通确认(单条同步) :发一条等一条的确认,效率低;
  • 批量确认:批量发送后等批量确认,效率高但可能丢失批量内的部分消息;
  • 异步确认:发送消息后不阻塞,通过回调处理确认结果,效率最高(推荐)。

工作流程(异步确认,主流方案)

代码实现(Spring AMQP 异步确认)

yaml 复制代码
spring:
  rabbitmq:
    publisher-returns: true
    publisher-confirm-type: correlated
typescript 复制代码
@Configuration
public class RabbitPublisherConfig {
    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        // 开启生产者确认
        rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
            if (ack) {
                // 确认成功:correlationData是消息唯一标识,可用于记录消息状态
                System.out.println("消息发送成功,ID:" + correlationData.getId());
            } else {
                // 确认失败:触发重试或写入死信表
                System.out.println("消息发送失败,原因:" + cause + ",ID:" + correlationData.getId());
                // 这里可调用重试逻辑,或把消息存入数据库后续补偿
            }
        });
        // 开启消息路由失败通知(比如交换机不存在、路由键匹配不到队列)
        rabbitTemplate.setReturnsCallback(returnedMessage -> {
            System.out.println("消息路由失败:" + returnedMessage.getMessage());
        });
        // 必须设置为true,否则路由失败时消息直接丢弃,不会触发returnsCallback
        rabbitTemplate.setMandatory(true);
        return rabbitTemplate;
    }
}

// 生产者发送消息示例
@Service
public class ProducerService {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    public void sendMessage(String msg) {
        // 生成唯一消息ID,用于确认回调
        CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
        // 发送消息:交换机、路由键、消息体、唯一标识
        rabbitTemplate.convertAndSend("demo.exchange", "demo.key", msg, correlationData);
    }
}

二、MQ 自身可靠性:确保消息 "存得住、不丢失"

MQ 自身可靠性核心是数据持久化 (避免 MQ 宕机丢失消息)和LazyQueue(优化海量消息存储,避免内存溢出)。

1. 数据持久化

原理

RabbitMQ 默认消息只存内存,宕机后全部丢失;持久化机制通过 "三层持久化"(交换机、队列、消息)将数据写入磁盘,MQ 重启后可恢复。

  • 交换机持久化:声明交换机时标记durable=true,MQ 重启后交换机仍存在;
  • 队列持久化:声明队列时标记durable=true,队列的元数据(名称、路由规则)持久化;
  • 消息持久化:发送消息时标记deliveryMode=2(持久化),消息内容写入磁盘。

工作流程

代码实现(三层持久化)

typescript 复制代码
@Configuration
public class RabbitQueueConfig {
    // 1. 声明持久化交换机
    @Bean
    public DirectExchange durableExchange() {
        // durable=true:持久化;autoDelete=false:不自动删除
        return new DirectExchange("demo.exchange", true, false);
    }
    
    // 2. 声明持久化队列
    @Bean
    public Queue durableQueue() {
        // durable=true:持久化;exclusive=false:非独占;autoDelete=false:不自动删除
        return QueueBuilder.durable("demo.queue").build();
    }
    
    // 3. 绑定交换机和队列
    @Bean
    public Binding binding(DirectExchange durableExchange, Queue durableQueue) {
        return BindingBuilder.bind(durableQueue).to(durableExchange).with("demo.key");
    }
}

// 发送持久化消息(Spring AMQP默认deliveryMode=2,无需手动设置)
@Service
public class ProducerService {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    public void sendPersistentMsg(String msg) {
        rabbitTemplate.convertAndSend("demo.exchange", "demo.key", msg, message -> {
            // 显式设置消息持久化(可选,Spring默认已设置)
            message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
            return message;
        });
    }
}

2. LazyQueue(惰性队列)

原理

默认队列(Classic Queue)会优先将消息存入内存,仅在内存不足时刷盘;而 LazyQueue(惰性队列)优先将消息写入磁盘,只有消费者消费时才加载到内存,核心解决 "海量消息堆积导致 MQ 内存溢出" 的问题。适用场景:消息堆积量大、消费速度慢(比如秒杀场景的延迟消费、批量处理)。

工作流程

代码实现(声明 LazyQueue)

typescript 复制代码
@Configuration
public class RabbitLazyQueueConfig {
    @Bean
    public Queue lazyQueue() {
        // 通过QueueBuilder声明惰性队列
        return QueueBuilder.durable("demo.lazy.queue")
                .lazy() // 核心:标记为LazyQueue
                .build();
    }
    
    @Bean
    public Binding lazyBinding(DirectExchange durableExchange, Queue lazyQueue) {
        return BindingBuilder.bind(lazyQueue).to(durableExchange).with("lazy.key");
    }
}

注意:LazyQueue 的缺点是消费延迟略高(需要从磁盘加载),但内存占用极低,适合消息堆积场景;普通队列适合低延迟、高吞吐的实时消费场景。

三、消费者可靠性:确保消息 "收得到、处理完"

消费者可靠性核心解决两个问题:确认消息已处理完成处理失败时自动重试

1. 消费者确认(Consumer ACK)

原理

消费者接收消息后,必须主动向 MQ 发送 "确认回执"(ACK),MQ 只有收到 ACK 才会删除消息;若消费者宕机 / 处理失败未发送 ACK,MQ 会将消息重新分发给其他消费者(避免消息丢失)。RabbitMQ 提供三种确认模式:

工作流程(手动确认,生产环境必用)

代码实现(Spring AMQP 手动确认)

scss 复制代码
@Configuration
public class RabbitConsumerConfig {
    @Bean
    public SimpleMessageListenerContainer listenerContainer(ConnectionFactory connectionFactory) {
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
        container.setQueueNames("demo.queue");
        // 核心:关闭自动确认,开启手动确认
        container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
        // 设置消费者数量、并发数等
        container.setConcurrentConsumers(2);
        container.setMaxConcurrentConsumers(5);
        // 消费逻辑
        container.setMessageListener((ChannelAwareMessageListener) (message, channel) -> {
            try {
                // 1. 解析消息
                String msg = new String(message.getBody(), StandardCharsets.UTF_8);
                System.out.println("消费消息:" + msg);
                // 2. 执行业务逻辑(比如入库、调用接口)
                // doBusiness();
                // 3. 手动确认:第二个参数multiple=false表示只确认当前消息
                channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
            } catch (Exception e) {
                // 4. 处理失败:否定确认,重新投递(第三个参数requeue=true)
                // 注意:若业务是幂等的,可重新投递;若非幂等,需先保证幂等再重试
                channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
                // 若不想重新投递,设置requeue=false,消息会进入死信队列
                // channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
            }
        });
        return container;
    }
}

2. 失败重试机制

原理

消费者处理消息失败时,通过 "重试策略" 自动重新处理消息,避免消息直接进入死信队列;重试次数耗尽后,再将消息移入死信队列(DLQ),便于后续人工排查。Spring AMQP 的重试机制基于RetryTemplate,支持 "固定间隔重试""指数退避重试" 等策略。

工作流程

代码实现(失败重试 + 死信队列)

typescript 复制代码
@Configuration
public class RabbitRetryConfig {
    // 1. 声明死信交换机和死信队列
    @Bean
    public DirectExchange dlxExchange() {
        return new DirectExchange("demo.dlx.exchange", true, false);
    }
    
    @Bean
    public Queue dlxQueue() {
        return QueueBuilder.durable("demo.dlx.queue").build();
    }
    
    @Bean
    public Binding dlxBinding(DirectExchange dlxExchange, Queue dlxQueue) {
        return BindingBuilder.bind(dlxQueue).to(dlxExchange).with("dlx.key");
    }
    
    // 2. 声明业务队列,并绑定死信交换机
    @Bean
    public Queue businessQueue() {
        return QueueBuilder.durable("demo.business.queue")
                // 绑定死信交换机
                .deadLetterExchange("demo.dlx.exchange")
                // 死信路由键
                .deadLetterRoutingKey("dlx.key")
                .build();
    }
    
    // 3. 配置消费者重试策略
    @Bean
    public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);
        // 关闭自动确认
        factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
        // 开启重试
        factory.setRetryTemplate(retryTemplate());
        // 重试耗尽后是否重新入队(设为false,让消息进入死信队列)
        factory.setDefaultRequeueRejected(false);
        return factory;
    }
    
    // 自定义重试策略(指数退避:重试间隔越来越长)
    @Bean
    public RetryTemplate retryTemplate() {
        RetryTemplate template = new RetryTemplate();
        // 重试次数:最大3次
        SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
        retryPolicy.setMaxAttempts(3);
        template.setRetryPolicy(retryPolicy);
        // 指数退避策略:初始间隔1秒,每次乘以2,最大间隔10秒
        ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
        backOffPolicy.setInitialInterval(1000);
        backOffPolicy.setMultiplier(2);
        backOffPolicy.setMaxInterval(10000);
        template.setBackOffPolicy(backOffPolicy);
        return template;
    }
}

// 消费者监听示例
@Component
public class ConsumerService {
    @RabbitListener(queues = "demo.business.queue", containerFactory = "rabbitListenerContainerFactory")
    public void consume(Message message, Channel channel) throws IOException {
        try {
            String msg = new String(message.getBody(), StandardCharsets.UTF_8);
            System.out.println("消费消息:" + msg);
            // 模拟业务处理失败
            int a = 1 / 0;
            // 处理成功,手动确认
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        } catch (Exception e) {
            // 抛出异常触发重试,重试耗尽后消息进入死信队列
            throw new RuntimeException("处理消息失败", e);
        }
    }
    
    // 监听死信队列,处理重试耗尽的消息
    @RabbitListener(queues = "demo.dlx.queue")
    public void consumeDlx(Message message) {
        String msg = new String(message.getBody(), StandardCharsets.UTF_8);
        System.out.println("处理死信消息(重试耗尽):" + msg);
        // 这里可记录日志、告警、人工介入处理
    }
}

总结

  1. 生产者可靠性:重连靠 "连接监听 + 重试策略" 保证链路不中断,生产者确认靠 MQ 回执确保消息送达 MQ,异步确认是生产环境最优选择;
  2. MQ 自身可靠性:三层持久化(交换机 + 队列 + 消息)保证宕机不丢数据,LazyQueue 优先存磁盘解决海量消息内存溢出问题;
  3. 消费者可靠性:手动 ACK 确保消息处理完成后才删除,失败重试(配合死信队列)避免消息直接丢失,重试耗尽后需人工兜底。

核心原则:RabbitMQ 的可靠性是 "分层保障",需同时做好生产者、MQ、消费者三层的可靠性配置,缺一不可;生产环境禁用自动 ACK、禁用非持久化消息,确保每一步都有确认和兜底机制。

相关推荐
小码哥_常2 小时前
别再乱加exclusion了!Maven依赖冲突有妙解
后端
狂奔小菜鸡2 小时前
Day39 | Java中更灵活的锁ReentrantLock
java·后端·java ee
IvanCodes2 小时前
一、消息队列理论基础与Kafka架构价值解析
大数据·后端·kafka
Nyarlathotep01132 小时前
gin03:请求中的参数
后端·go
用户7344028193422 小时前
java通过SpringBoot操作elasticsearch实现基本的增删改查
后端
小码哥_常2 小时前
一文带你吃透@Async,让异步编程so easy!
后端
元亨利贞4482 小时前
C#中空值校验情况说明
后端
shark_chili2 小时前
Spring AI alibaba最佳实践-jvm监控诊断agent开发教程
后端
颜酱2 小时前
从0到1实现LRU缓存:思路拆解+代码落地
javascript·后端·算法