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

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.dtcms.com/a/347935.html

相关文章:

  • 阿里云拉取dockers镜像
  • 在JavaScript中,比较两个数组是否有相同元素(交集)的常用方法
  • 今日科技热点 | AI加速创新,5G与量子计算引领未来
  • wpf之DockPanel
  • 3D打印机管理后台与RabbitMQ集成的业务场景
  • RabbitMQ面试精讲 Day 29:版本升级与平滑迁移
  • 【图像处理基石】基于 Python 的图像行人删除技术:实现街景无干扰化处理
  • 性能比拼: .NET (C#) vs. Fiber (Go)
  • Kaggle项目:一次 Uber 出行数据分析的完整思路
  • 高空作业安全监控难题突破!陌讯自适应识别算法实现安全带穿戴检测准确率↑93%
  • 深度学习——详细教学:神经元、神经网络、感知机、激活函数、损失函数、优化算法(梯度下降)
  • 大数据管理与应用系列丛书《数据挖掘》读书笔记之集成学习(1)
  • 基于PHP服装租赁管理系统/基于php的服装管理系统的设计与实现
  • 基于电磁频谱地图的辐射源定位算法复现
  • 算法训练营day60 图论⑩ Bellman_ford 队列优化算法、判断负权回路、单源有限最短路(修改后版本)
  • [两数之和](哈希表做法)
  • priority_queue和仿函数
  • Trip Footprint旅行足迹App技术架构全解析
  • 题解:P13754 【MX-X17-T3】Distraction_逆序对_前缀和_Ad-hoc_算法竞赛C++
  • GECP高程控制点数据集进行展示
  • 视觉革命:云渲染如何让创意不再受限于硬件
  • RustFS的边缘计算优化方案在5G MEC场景下的实测数据如何?
  • 迭代器模式与几个经典的C++实现
  • 双目密集匹配(stereo dense matching)
  • 从人工巡检到智能监测:工业设备管理的颠覆性变革
  • 97. 小明逛公园,Floyd 算法,127. 骑士的攻击,A * 算法
  • [Redis进阶]---------持久化
  • std::uncaught_exceptions 详解
  • 大模型——深度评测智能体平台Coze Studio
  • 【Cmake】cmake_minimum_required,project,include,install,add_executable