当前位置: 首页 > ops >正文

RabbitMQ--消费端异常处理与 Spring Retry

1. 消息确认机制(ack)

RabbitMQ 消息投递到消费者后,必须确认(ack)才能从队列中移除:

  • auto-ack = true

    • 消息一投递就算消费成功。

    • 如果消费者宕机,消息会丢失。

    • 一般不用。

  • manual-ack = false(默认)

    • 由 Spring AMQP 或手动调用 basicAck 来确认。

    • 消费成功 → basicAck

    • 消费失败 → basicNackbasicReject

    • 是否重回队列取决于 requeue 参数。


2. Spring Retry 机制

捕获位置

  • Spring Retry 通过 AOP 代理在方法外部包裹一个“重试拦截器”

  • 异常必须从方法栈顶抛出到代理外层才能被捕获

  • 方法内部 try-catch 捕获的异常 不会冒泡到代理外层 → Retry 无法捕获

如何才能触发

  • 必须在 @RabbitListener 方法上出现异常并且不处理,Spring Retry 才能捕获并重试

  • 方法内部捕获异常或自己处理掉 → Retry 无法触发

Spring Boot 已内置 RetryTemplate,只要配置就能在消费者异常时自动重试。

配置示例(application.yml)

 acknowledge-mode: auto --》消息会在消费者方法执行完毕后被自动确认(ACK)

spring:rabbitmq:listener:simple:acknowledge-mode: auto# 一般用自动ack 消息会在消费者方法执行完毕后被自动确认(ACK)retry:enabled: true            # 开启消费者重试max-attempts: 5          # 最大重试次数initial-interval: 1000   # 第一次重试间隔 1smultiplier: 2.0          # 重试间隔倍数(指数退避)max-interval: 10000      # 最大重试间隔 10s

消费者示例

@Component
public class RetryConsumer {@RabbitListener(queues = "test.retry.queue")public void onMessage(String msg) {System.out.println("收到消息:" + msg);// 模拟业务异常if (msg.contains("error")) {throw new RuntimeException("消费失败,触发Spring Retry");}System.out.println("消费成功:" + msg);}
}

执行流程

  1. 第一次失败 → 等待 1s 后再次执行。

  2. 第二次失败 → 等待 2s 后再次执行。

  3. 第三次失败 → 等待 4s 后再次执行。

  4. …直到 max-attempts 用完。

  5. 超过最大次数 → 调用 RecoveryCallback(默认是丢弃或进入 DLQ)。

👉 注意:Spring Retry 只在 消费者方法抛异常 时才会触发。如果内部用try-catch处理了没有抛出则不会触发Spring Retry
👉 这里也可以把重试几次看做重复消费几次,以及重试的话也会多次执行相同的业务代码


3. 手动 Nack + DLQ(推荐生产场景)

有时我们不想依赖 Spring Retry,而是用 手动 nack 配合 死信队列(DLQ) 遇到异常如何处理。

配置队列(带 DLQ)

@Configuration
public class RabbitConfig {@Beanpublic Queue businessQueue() {return QueueBuilder.durable("test.dlx.queue").withArgument("x-dead-letter-exchange", "dlx.exchange") // 绑定死信交换机.withArgument("x-dead-letter-routing-key", "dlx.key").build();}@Beanpublic DirectExchange dlxExchange() {return new DirectExchange("dlx.exchange");}@Beanpublic Queue deadLetterQueue() {return new Queue("dlx.queue");}@Beanpublic Binding bindingDLQ() {return BindingBuilder.bind(deadLetterQueue()).to(dlxExchange()).with("dlx.key");}
}

消费者(手动控制 ack/nack)

@Component
public class DLQConsumer {@RabbitListener(queues = "test.dlx.queue")public void onMessage(String msg, Channel channel, Message message) throws IOException {try {System.out.println("收到消息:" + msg);if (msg.contains("error")) {throw new RuntimeException("消费失败,进入DLQ");}// 成功手动确认channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);} catch (Exception e) {System.err.println("消费异常:" + e.getMessage());// 失败:不重回队列,直接进入 DLQchannel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);}}
}

这里就算在yml中定义重试也没有作用,原因如下:

  1. Spring Retry 的工作时机

    • 当你配置了 retry: enabled: true 时,Spring会创建一个代理(AOP Around Advice)来包裹你的 @RabbitListener 方法。

    • 这个代理的逻辑是:当你的监听方法抛出异常,它才会捕获这个异常,并根据配置进行重试(等待间隔、重试次数等)。

    • 在所有重试次数用尽后,如果仍然失败,这个代理会抛出一个 AmqpRejectAndDontRequeueException 异常,这会触发RabbitMQ将消息拒绝并送入死信队列(DLQ)。

  2. 你的代码做了什么

    • 你在方法内部使用了 try-catch捕获了所有异常(Exception e

    • 在 catch 块中,你直接调用了 channel.basicNack(...) 手动拒绝了消息。

    • 关键点:由于异常被你亲手捕获并处理了,它并没有被抛出到方法之外。因此,外层的Spring Retry代理根本看不到任何异常,它认为本次消费已经“成功”处理完毕(尽管是手动Nack了),所以重试机制完全没有机会触发。


4. 对比总结

方案原理配置复杂度重试策略消息去向适用场景
Spring RetrySpring AMQP 捕获异常,内部调度重试简单(yml 配置即可)指数退避/固定间隔超过次数 → 默认丢弃或进入 DLQ开发测试、简单重试需求
手动 Nack + DLQ消费失败 → basicNack(requeue=false) → 死信队列 → 再投递较复杂(需要DLQ配置)由 TTL + DLQ 控制(灵活)失败消息进入 DLQ,便于监控和人工处理生产环境,严格保证消息不丢失

5. 推荐做法(生产级)

  1. 不要依赖 auto-ack,统一用 manual-ack

  2. 开发阶段 → 可以用 Spring Retry 简单实现。

  3. 生产环境 → 建议用 DLQ + TTL 延时重试,可控性强,防止消息丢失。

  4. 关键业务 → 搭配消息追踪 & 异常告警。

http://www.xdnf.cn/news/18605.html

相关文章:

  • 2025最新ncm转MP3,网易云ncm转mp3格式,ncm转mp3工具!
  • ThinkPHP8学习篇(四):请求和响应
  • VSCode无权访问扩展市场
  • 【数据结构】-5- 顺序表 (下)
  • 【JavaEE】了解synchronized
  • Java 基础学习总结(211)—— Apache Commons ValidationUtils:让参数校验从 “体力活“ 变 “优雅事“
  • 电动车运行原理与最新人工智能驾驶技术在电动车上的应用展望:从基础动力系统到L5级完全自动驾驶的技术深度解析
  • 大语言模型的自动驾驶 LMDrive/DriveVLM-Dual
  • Kubernetes部署Prometheus+Grafana 监控系统NFS存储方案
  • Spark04-MLib library01-机器学习的介绍
  • Spring创建的方式
  • 在 Ubuntu 24.04 或 22.04 LTS 服务器上安装、配置和使用 Fail2ban
  • 【LLM】DeepSeek-V3.1-Think模型相关细节
  • Android - 用Scrcpy 将手机投屏到Windows电脑上
  • MySQL学习记录-基础知识及SQL语句
  • SSRF的学习笔记
  • React useState 全面深入解析
  • 6.2 el-menu
  • Axure RP 9的安装
  • 如何让FastAPI在百万级任务处理中依然游刃有余?
  • Postman参数类型、功能、用途及 后端接口接收详解【接口调试工具】
  • 【C++】函数返回方式详解:传值、传引用与传地址
  • Linux 824 shell:expect
  • 今日科技热点 | AI加速创新,5G与量子计算引领未来
  • PHP - 实例属性访问与静态方法调用的性能差异解析
  • B站视频字幕提取工具
  • mysql 5.7 查询运行时间较长的sql
  • 【计算机408数据结构】第三章:基本数据结构之栈
  • 苍穹外卖项目实战(日记十)-记录实战教程及问题的解决方法-(day3-2)新增菜品功能完整版
  • 启动Flink SQL Client并连接到YARN集群会话