news 2026/7/7 6:58:40

RabbitMQ 消息队列积压告警:我用消费者并发 + 死信队列把积压从 10 万条清空到 0

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
RabbitMQ 消息队列积压告警:我用消费者并发 + 死信队列把积压从 10 万条清空到 0

RabbitMQ 消息队列积压告警:我用消费者并发 + 死信队列把积压从 10 万条清空到 0

凌晨两点,手机震了。

Prometheus 告警:RabbitMQorder_queue消息积压超过 10 万条,消费速率只有 120 msg/s,生产速率 850 msg/s。再不管,队列内存撑爆只是时间问题。

我之前写过 Kafka 消费延迟的排查,但 RabbitMQ 的积压治理完全是另一套逻辑。这次用了 40 分钟把积压清空到 0,核心就两件事:消费者并发提上去,死信队列把烂消息隔离出去

问题背景:为什么 Kafka 那套不顶用了

我们的订单系统用 RabbitMQ 做异步解耦,上游订单服务生产消息,下游库存、物流、财务三个服务消费。平时队列深度稳定在 500 条以内,但那天晚上突然炸了。

先说一个认知误区:很多人觉得消息队列积压了,加消费者就行了。这在 Kafka 里确实好使,因为分区机制天然支持横向扩容。但 RabbitMQ 的队列是单线程的,一个队列只能被一个消费者串行处理,加机器没用,得加并发通道

另外,RabbitMQ 的默认prefetch_count是无限,消费者一次拉走所有消息,如果处理一条消息卡住,整个通道都堵死。这和 Kafka 的拉取模式完全不同。

排查:5 分钟定位瓶颈

登录 RabbitMQ Management UI,先看三个指标:

  • Ready:10.2 万条消息待消费
  • Unacked:187 条(消费者已拉取但未确认)
  • Consumer utilisation:12.3%(消费者利用率极低)

Unack 只有 187,但 Ready 有 10 万,说明消费者没卡死,就是处理速度太慢

SSH 到消费服务节点,先看消费者进程:

rabbitmqctl list_queues name messages_ready messages_unacknowledged consumers# order_queue 102347 187 3

只有 3 个消费者,但每个消费者是单线程串行处理。进一步看消费者配置:

rabbitmqctl list_consumers|greporder_queue# 3 个 consumer,channel 都是同一个连接

问题找到了:应用虽然启动了 3 个消费者实例,但每个实例都是单线程,而且共用同一个连接,实际并发度是 3,根本扛不住 850 msg/s 的生产速率。

还有一个隐藏问题:积压的消息里混了一批异常消息。下游物流系统调用第三方 API 超时,消息处理失败进入重试,每次重试 30 秒,直接把消费者拖死了。

解决方案:三层并发 + 死信队列

第一层:提高消费者并发度

把每个消费者实例从单线程改成多线程,并且每个线程独占一个 channel。Spring AMQP 里的配置是这样的:

@BeanpublicSimpleRabbitListenerContainerFactoryrabbitListenerContainerFactory(ConnectionFactoryconnectionFactory){SimpleRabbitListenerContainerFactoryfactory=newSimpleRabbitListenerContainerFactory();factory.setConnectionFactory(connectionFactory);// 每个消费者实例并发 10 个线程factory.setConcurrentConsumers(10);factory.setMaxConcurrentConsumers(20);// 每次预取 50 条,避免单个消费者堆积factory.setPrefetchCount(50);// 手动 ACK,确保消息不丢失factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);returnfactory;}

关键参数解释:

  • concurrentConsumers=10:每个消费者容器启动 10 个并发线程
  • maxConcurrentConsumers=20:负载高时自动扩容到 20 个
  • prefetchCount=50:每个消费者一次只拉 50 条,处理完再拉,避免某个消费者饿死

改完重启,消费者从 3 个变成 30 个(3 个实例 × 10 线程),并发度直接翻 10 倍。

第二层:死信队列隔离异常消息

光加并发不够,那批超时的异常消息还在反复重试,只要有一条卡住,整个线程就废了。需要把处理失败的消息踢出去,别让一颗老鼠屎坏了一锅粥。

RabbitMQ 的死信交换机(DLX)机制正好干这个。思路是:主队列消费失败的消息,超过最大重试次数后,自动路由到死信队列,避免阻塞正常消费。

@BeanpublicQueueorderQueue(){Map<String,Object>args=newHashMap<>();// 消息在队列存活超过 60 秒仍未消费,进入死信args.put("x-message-ttl",60000);// 最大重试 3 次args.put("x-dead-letter-exchange","order.dlx.exchange");args.put("x-dead-letter-routing-key","order.dlx.routing.key");returnQueueBuilder.durable("order_queue").withArguments(args).build();}@BeanpublicQueueorderDlxQueue(){returnQueueBuilder.durable("order_dlx_queue").build();}@BeanpublicDirectExchangeorderDlxExchange(){returnnewDirectExchange("order.dlx.exchange");}@BeanpublicBindingorderDlxBinding(){returnBindingBuilder.bind(orderDlxQueue()).to(orderDlxExchange()).with("order.dlx.routing.key");}

消费端加上重试和死信逻辑:

@RabbitListener(queues="order_queue",containerFactory="rabbitListenerContainerFactory")publicvoidhandleOrder(Messagemessage,Channelchannel)throwsIOException{longdeliveryTag=message.getMessageProperties().getDeliveryTag();try{// 业务处理processOrder(newString(message.getBody()));channel.basicAck(deliveryTag,false);}catch(Exceptione){// 重试次数从消息头里取IntegerretryCount=(Integer)message.getMessageProperties().getHeaders().getOrDefault("x-retry-count",0);if(retryCount>=3){// 超过 3 次,拒绝消息并送入死信队列channel.basicReject(deliveryTag,false);}else{// 重新发布,retry count +1message.getMessageProperties().setHeader("x-retry-count",retryCount+1);channel.basicAck(deliveryTag,false);rabbitTemplate.send("order_queue",message);}}}

这样异常消息最多重试 3 次,之后就进死信队列,主队列的消费通道彻底解放。

第三层:死信队列的补偿消费

死信队列里的消息不能不管,需要人工或定时任务补偿处理。我加了一个简单的补偿消费者,专门处理死信:

@RabbitListener(queues="order_dlx_queue",concurrency="3-5")publicvoidhandleDlx(Messagemessage,Channelchannel)throwsIOException{longdeliveryTag=message.getMessageProperties().getDeliveryTag();try{// 死信补偿:记录日志 + 通知人工介入log.warn("死信消息补偿: {}",newString(message.getBody()));// 落库到补偿表,后续人工处理或重试dlxCompensationService.save(message);channel.basicAck(deliveryTag,false);}catch(Exceptione){// 补偿失败,直接丢弃,避免死信队列也积压channel.basicReject(deliveryTag,false);}}

死信消息的处理原则是:不能再重试回主队列,否则又会堵死。要么人工处理,要么走降级流程。

效果:40 分钟从 10 万条清空到 0

改完配置重启后,RabbitMQ 面板的变化:

时间点Ready 消息数消费速率消费者数
02:00102,347120 msg/s3
02:1587,2101,200 msg/s30
02:3034,5001,850 msg/s30
02:400850 msg/s30

积压清空的那一刻,消费速率和生产速率持平,队列深度稳定为 0。

更关键的是,死信队列把 23 条异常消息隔离了出去,主队列再也不被卡住。

写在最后

这次排查和 Kafka 那次最大的不同,是 RabbitMQ 的队列模型决定了你不能靠堆机器解决积压,得从并发通道和消息隔离两个维度动手。

三个经验:

  1. prefetchCount 必须设上限。默认无限的话,一个消费者可能把所有消息都拉走,其他消费者干看着。
  2. 死信队列不是可选项,是必选项。任何生产环境的 RabbitMQ 都必须配死信,否则异常消息就是定时炸弹。
  3. 监控不要只看队列深度。消费者利用率(Consumer utilisation)和 Unacked 数比 Ready 数更能反映瓶颈在哪。

如果这篇文章对你有帮助,欢迎收藏。下次遇到 RabbitMQ 积压,至少知道该改哪几个参数了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/7 6:56:39

如何高效使用LabelPlus:专业的漫画翻译辅助工具完整指南

如何高效使用LabelPlus&#xff1a;专业的漫画翻译辅助工具完整指南 【免费下载链接】LabelPlus Easy tool for comic translation. 项目地址: https://gitcode.com/gh_mirrors/la/LabelPlus LabelPlus是一款专为漫画翻译设计的辅助工具&#xff0c;它通过简化的标签管理…

作者头像 李华
网站建设 2026/7/7 6:55:55

Qwen图像编辑AI加速器:从v1到v23的完整版本选择指南

Qwen图像编辑AI加速器&#xff1a;从v1到v23的完整版本选择指南 【免费下载链接】Qwen-Image-Edit-Rapid-AIO 项目地址: https://ai.gitcode.com/hf_mirrors/Phr00t/Qwen-Image-Edit-Rapid-AIO Qwen-Image-Edit-Rapid-AIO是一个专为快速AI图像编辑设计的完整解决方案&a…

作者头像 李华
网站建设 2026/7/7 6:54:37

2026年东莞良心4s店推荐:最新权威排名与专业指南。

截至2026年&#xff0c;虽然没有提供一个官方的东莞4S店综合排名列表&#xff0c;但根据市场表现、用户反馈及专业评价&#xff0c;可以提炼出一些在东莞地区具有较高声誉和服务质量的4S店信息。这些店铺涵盖了不同的汽车品牌&#xff0c;并且以其特色服务或优势著称。以下是几…

作者头像 李华
网站建设 2026/7/7 6:53:55

5G边缘控制无人机在地下矿山的实战部署与技术突破

1. 项目概述&#xff1a;为什么地下矿山成了5G边缘控制无人机的“终极考场”“5G边缘控制无人机在地下矿山的首次实地部署”——这个标题里没有一个字是虚的&#xff0c;全是硬指标、真场景、高门槛。我干了十多年工业自动化与无线通信集成项目&#xff0c;从早期的Wi-Fi巡检小…

作者头像 李华
网站建设 2026/7/7 6:53:33

Terax:7MB轻量级AI原生开发环境全解析与实战指南

&#x1f680; 30款热门AI模型一站整合&#xff0c;DeepSeek/GLM/Qwen 随心用&#xff0c;限时 5 折。 &#x1f449; 点击领海量免费额度 在终端开发工具日益臃肿的今天&#xff0c;一个仅7MB大小的轻量级AI原生开发环境Terax引起了广泛关注。作为基于Tauri 2 Rust和React…

作者头像 李华