Spring Boot + RabbitMQ 消息100%可靠?3大绝招 + 手动 ACK,彻底告别丢失!

开发 架构
我们还提到 死信队列 及 消息轨迹监控 作为生产级增强方案,进一步提升系统稳定性。如果你在生产环境中使用 RabbitMQ,建议你根据本篇内容进行配置,确保消息 零丢失、零误判,构建更加健壮的消息队列架构。

在分布式系统架构中,RabbitMQ 作为强大的消息中间件,广泛应用于订单、库存、支付等核心业务场景。然而,消息丢失问题时有发生,例如:

  • 订单支付通知丢失导致客户已付款但系统未更新状态;
  • 库存扣减消息丢失导致库存数据与实际销量不一致;
  • 消息无法到达消费者导致业务流程中断,严重影响用户体验。

要解决这些问题,我们需要建立 高可靠性的 RabbitMQ 消息传输机制,确保消息 生产、存储、消费 三个环节的稳定性。本文将基于 Spring Boot 3.4,介绍 生产者确认机制、消息持久化、手动 ACK 三大核心策略,并提供完整的可运行代码示例,帮助你彻底告别 RabbitMQ 消息丢失。

消息丢失的3大“案发现场”

生产者消息投递失败

  • 问题原因网络抖动、RabbitMQ 服务宕机、路由配置错误;
  • 后果消息未成功发送,导致数据不一致;
  • 解决方案生产者 Confirm 模式 + Return 回调 机制。

MQ 服务崩溃

  • 问题原因RabbitMQ 服务器故障、磁盘损坏、未开启消息持久化;
  • 后果未持久化的消息在 RabbitMQ 宕机后丢失;
  • 解决方案交换机、队列、消息持久化,保证消息不会因重启而丢失。

消费者崩溃

  • 问题原因:消费者在处理消息时异常退出,或者自动 ACK 机制导致 RabbitMQ 认为消息已消费;
  • 后果:消息被 RabbitMQ 移除,但实际业务未处理成功;
  • 解决方案手动 ACK + 幂等性控制,确保消息消费的可靠性。

生产者可靠性:Confirm模式 + Return机制

启用Confirm与Return机制

spring:
  rabbitmq:
    publisher-confirm-type: correlated  # 启用Confirm模式
    publisher-returns: true             # 启用Return机制
    template:
      mandatory: true                   # 让生产者接收未被路由的消息通知
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

实现Confirm回调(确保消息成功落库)

package com.icoderoad.mq;


import com.icoderoad.mapper.MessageLogMapper;
import com.icoderoad.model.MessageStatus;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.ReturnedMessage;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;


@Slf4j
@Component
public class MqConfirmCallback implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnsCallback {


    @Autowired
    private MessageLogMapper messageLogMapper;


    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        if (ack) {
            log.info("✅ 消息到达 Exchange,ID:{}", correlationData.getId());
            messageLogMapper.updateStatus(correlationData.getId(), MessageStatus.SENT);
        } else {
            log.error("❌ 消息投递失败,ID:{},原因:{}", correlationData.getId(), cause);
        }
    }


    @Override
    public void returnedMessage(ReturnedMessage returned) {
        log.error("🚨 消息路由失败!交换机:{},路由键:{},消息:{}",
                returned.getExchange(), returned.getRoutingKey(), new String(returned.getMessage().getBody()));
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.

发送消息(携带唯一消息ID)

public void sendOrder(Order order) {
    String msgId = UUID.randomUUID().toString();
    messageLogMapper.insert(new MessageLog(msgId, order, MessageStatus.SENDING));


    rabbitTemplate.convertAndSend(
        "order-exchange", 
        "order.create", 
        order, 
        message -> {
            message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
            message.getMessageProperties().setMessageId(msgId);
            return message;
        },
        new CorrelationData(msgId)
    );
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.

MQ可靠性:队列/消息持久化

package com.icoderoad;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitMqConfig {


    @Bean
    public DirectExchange orderExchange() {
        return new DirectExchange("order-exchange", true, false);
    }


    @Bean
    public Queue orderQueue() {
        return new Queue("order.queue", true);
    }


    @Bean
    public Binding orderBinding() {
        return BindingBuilder.bind(orderQueue()).to(orderExchange()).with("order.create");
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.

消费者可靠性:手动ACK + 幂等性

关闭自动ACK,改为手动

spring:
  rabbitmq:
    listener:
      simple:
        acknowledge-mode: manual  # 开启手动ACK
        prefetch: 10              # 限制单次拉取消息数
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

处理订单消息(确保幂等性 + 手动ACK

package com.icoderoad.consumer;


import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.messaging.handler.annotation.Header;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.support.AmqpHeaders;
import java.util.concurrent.TimeUnit;


@Slf4j
@Component
public class OrderConsumer {


    @Autowired
    private OrderService orderService;
    @Autowired
    private RedisTemplate<String, String> redisTemplate;


    @RabbitListener(queues = "order.queue")
    public void handleOrder(Order order, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long tag) {
        try {
            String redisKey = "order:" + order.getOrderNo();
            if (redisTemplate.opsForValue().setIfAbsent(redisKey, "processing", 30, TimeUnit.MINUTES)) {
                orderService.process(order);
                redisTemplate.delete(redisKey);
                channel.basicAck(tag, false);
                log.info("🎉 订单处理成功:{}", order.getOrderNo());
            } else {
                log.warn("⚠️ 订单正在处理中,直接ACK:{}", order.getOrderNo());
                channel.basicAck(tag, false);
            }
        } catch (Exception e) {
            log.error("❌ 订单处理失败:{}", order.getOrderNo(), e);
            channel.basicNack(tag, false, true);
        }
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.

全链路监控(生产环境推荐)

  1. 消息追踪记录消息流转状态
  2. 死信队列避免消息无限重试
  3. 消息监控Grafana监控消息积压、ACK率、重试次数
@Bean
public Queue orderQueue() {
    return QueueBuilder.durable("order.queue")
            .deadLetterExchange("dlx.exchange")
            .deadLetterRoutingKey("dlx.order")
            .build();
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

核心配置一览:

配置项

作用

publisher-confirm-type

确认消息到达Exchange

publisher-returns

监听未路由消息

队列持久化

确保消息重启后不丢失

acknowledge-mode=manual

关闭自动ACK,使用手动确认

delivery-mode=PERSISTENT

确保消息持久化

结论

在高并发分布式系统中,RabbitMQ 消息可靠性 直接关系到业务数据的完整性和一致性。本篇文章介绍了 Spring Boot 3.4 下 RabbitMQ 100% 可靠消息传输方案,核心思路包括:

  1. 生产者端采用 Confirm 机制 + Return 回调,确保消息成功到达 RabbitMQ;
  2. RabbitMQ 服务器端开启 队列、交换机持久化,防止消息因宕机丢失;
  3. 消费者端使用 手动 ACK + 幂等控制,确保消息被正确消费。

此外,我们还提到 死信队列 及 消息轨迹监控 作为生产级增强方案,进一步提升系统稳定性。如果你在生产环境中使用 RabbitMQ,建议你根据本篇内容进行配置,确保消息 零丢失、零误判,构建更加健壮的消息队列架构。

责任编辑:武晓燕 来源: 路条编程
相关推荐

2022-07-27 18:34:32

RabbitMQ宕机服务器

2024-08-12 12:17:03

2020-10-14 08:36:10

RabbitMQ消息

2021-09-15 09:02:20

Spring 6Spring BootJava

2021-09-03 06:46:34

Spring 6pring Boot 项目

2024-05-09 08:04:23

RabbitMQ消息可靠性

2022-09-23 13:57:11

xxl-job任务调度中间件

2020-06-24 09:35:50

SpringSpring BooJava

2023-03-06 08:16:04

SpringRabbitMQ

2021-09-16 10:29:05

开发技能代码

2024-10-11 11:32:22

Spring6RSocket服务

2022-08-29 18:14:55

MQ数据不丢失

2024-07-03 11:33:02

2021-08-10 09:59:15

RabbitMQ消息微服务

2024-10-21 00:00:03

JavaScriptDate​API

2009-09-14 09:09:07

Delphi 2010

2023-11-15 16:46:04

内存Java

2024-01-30 08:01:15

RabbitMQ业务逻辑应用场景

2024-10-10 08:34:34

事务外包模式

2022-03-31 08:26:44

RocketMQ消息排查
点赞
收藏

51CTO技术栈公众号