我们为何构建 Qarote:一次队列饱和故障实录
消息来自凌晨 2 点 47 分。不是 PagerDuty 告警——而是一位客户发来的 Slack 消息。
“嘿,你们现在有问题吗?我们的任务已经一个多小时没有在处理了。”
整整一个小时。
我打开了 RabbitMQ 管理插件。队列深度图表呈现为一条垂直线。847,000 条消息。我们的主任务队列在无人值守的情况下,已经积压了整整六十三分钟。两个消费者显示已连接,但没有任何一个在处理任何消息。
三十秒内,我打开了四个标签页:管理插件、Grafana、CloudWatch,以及一个 Slack 线程——我的同事们也在从各自的仪表盘尝试理解同一件事。
没有人知道发生了什么。
工具告诉了我什么
管理插件告诉我的,只是我从图表上已经知道的:队列很深,消息速率为零,显示有两个消费者已连接。
它无法告诉我的:
- 这两个消费者是哪两个? 是我预期的 worker,还是上次部署遗留的僵尸连接?
- 它们为什么连接着却不处理消息? 是卡在某条消息上?在悄无声息地崩溃重连?还是在等待下游服务?
- 积压什么时候开始的? 图表精度太粗,无法精确定位问题开始的时刻——我无法判断积压是十分钟前还是两小时前开始的。
- 这是一个队列的孤立问题,还是级联故障? 我有十五个队列,但插件每次只显示一个。
- 队列里的消息长什么样? 在没有写一个临时消费者脚本的情况下,我无法检查哪怕一条消息——这意味着我根本没法确认是消息本身有问题,还是消费者有问题。
我切换到 Grafana。我们配置的 RabbitMQ Prometheus 指标提供了队列深度和消费者数量的时序数据,但采集间隔是 60 秒——这意味着我看到的数据已经滞后一分钟。而且仪表盘是按 broker 组织的,而不是按我真正需要的方式——一眼看出哪些队列当前表现异常。
故障发生四十分钟后,我终于定位到了根因:数据库连接池在高负载下耗尽了。消费者在拉取消息后,在第一次 DB 调用时悄悄失败,nack 消息但没有正确记录错误,然后重新入队——从管理插件来看,这就像“两个消费者已连接,吞吐量为零”的循环。
修复花了五分钟,诊断花了四十分钟。
真正的问题
自那次故障以来,我想了很多。
问题不在于我们有烂工具。管理插件对日常可见性确实有用,Prometheus + Grafana 也是合理的监控方案。问题在于,这些工具没有一个是为了回答我凌晨 3 点真正想问的问题而设计的:现在哪里出问题了,我该怎么办?
管理插件展示的是状态,Grafana 展示的是历史。没有一个工具展示的是因果关系。
要正确诊断一个 RabbitMQ 故障,你需要关联来自不同地方的信息:队列深度与消费者健康状态,消费者健康状态与消息确认速率,消息确认速率与具体卡住的队列。你需要看到哪些消费者在真正处理消息,哪些是连接着但处于冻结状态。你需要知道一个队列的积压是与消费者数量下降同时开始的,还是在此之前就开始了——因为这是两种不同的故障。
这些信息没有一项是自动呈现的。在故障期间,需要手动逐个标签页地拼凑,而且还得是那个知道该看哪里的人。
一个典型的生产环境 RabbitMQ 监控方案当时长这样:
- 管理插件 — Web UI,适合手动检查
- Prometheus 导出器 — 将 RabbitMQ 指标转换为可采集的端点
- Alertmanager — 将基于指标的告警路由到 Slack 或 PagerDuty
- Grafana — 趋势分析仪表盘
- 自定义脚本 — 通常是 Python,通常用于检查 DLQ 深度,因为以上工具默认都不擅长这件事
五个工具,用于一个 broker。每个都需要配置、维护,以及一个知道出错时该看哪个标签页的人。
这不是一个监控方案,这是考古学。
我们想要的替代方案
故障之后,我开始思考:如果专门为了回答“现在哪里出问题了?“而非”这里是所有指标,祝你好运“而设计一个 RabbitMQ 监控工具,它应该是什么样的?
有几件事显然不可或缺:
变化率,而不仅仅是深度。 一个持有 50,000 条消息且以 +500/秒增长的队列,意味着 90 分钟后会有一个凌晨 3 点的告警。同一个队列以 -1,000/秒下降则是健康的。单纯的深度是滞后指标——你需要速度来判断是正在走向故障,还是正在从故障中恢复。
消费者健康状况作为一等指标。 不只是“有多少消费者已连接”,而是“它们是否真的在处理消息?“一个连接但不确认消息的消费者就是坏的。这个区别在大多数监控工具中是不可见的。
真正能用于故障复盘的队列历史。 RabbitMQ 原生的数据保留窗口短且精度低。那天晚上我需要的,是以 5 分钟为间隔、保留 7 天的深度历史——能够精确告诉我积压是什么时候开始的,而不只是“现在很深”。这种精度直接改变了故障定界的方式:是慢慢泄露还是突然崩塌?
无需消费即可检查消息。 那天晚上,不写临时消费者脚本就无法确认消息本身是否有效。我需要能直接查看队列里的消息——读取消息头,检查 payload 结构——而不消费任何一条消息。仅这一个能力,就能把诊断时间缩短 15 分钟。
自动化的故障关联,而不是手动切换标签页。 那四十分钟的诊断,并不是四十分钟的深度思考——而是四十分钟在三个不同工具之间手动拼凑本来就存在的上下文。一个能自动将队列积压峰值、消费者下线和消息速率变化关联成单一时间线的工具,不是在变魔法——它只是自动完成了故障诊断中那部分机械性的工作。
基于前置指标的告警,而不仅仅是滞后指标。 当队列达到 500,000 条消息时触发的告警,主要用于确认你已经处于故障中。当队列增长速度超过消耗速度时触发的告警——当你还有五分钟余量时——才是真正有用的。
每天早晨投递到邮箱的健康摘要。 不是因为我每天早上都要盯着仪表盘,而是因为小的异常需要在变成凌晨 3 点的电话前被发现。一份包含队列深度趋势、消费者状态和过去 24 小时异常的每日摘要,意味着进入任何故障前都有背景信息,而不是一无所知。
无需 Prometheus。 不是因为 Prometheus 不好——对于通用基础设施监控它非常出色。但为了监控单个 RabbitMQ 集群而搭建一套完整的 Prometheus + Grafana + Alertmanager,对于还没有这套方案的团队来说门槛太高。
为什么我们选择构建而非修补现有工具
我认真研究过现有选项。有 RabbitMQ 的 Grafana 仪表盘模板,有带 RabbitMQ 集成的 SaaS APM 平台,有商业的 RabbitMQ 监控产品。
但没有一个是从“告诉我哪里出问题了,而不仅仅是发生了什么”这个前提出发构建的。
于是我们构建了 Qarote。
Qarote 是一款自托管的 RabbitMQ 监控工具,直接连接到管理 HTTP API——不需要 Prometheus 插件,不需要 YAML,不需要部署 agent。 MIT 许可的核心版本,用一条 Docker 命令,两分钟内完成部署。
如果那天晚上有 Qarote 在运行,故障会是这样:
- 第 1 分钟: 消费者已连接但未确认的标志触发。Qarote 每 15 秒轮询一次——没有 60 秒的采集延迟。
- 第 1 分钟: 队列增长速率告警触发。积压在加速,不只是“很深”。
- 第 2 分钟: 故障诊断引擎自动关联了队列积压峰值、消费者确认速率下降和应用报错激增——同一个 4 分钟窗口,全部自动完成。无需切换标签页。
- 第 5 分钟: Message Spy 确认堵塞队列中的消息结构完整——payload 正常,消息头正确。问题不在消息,在消费者。
- 第 5 分钟: 范围缩小到 DB 或网络。第 10 分钟,修复上线。
十分钟,不是四十分钟。
而第二天早上,每日摘要会把这次故障包含在 24 小时异常总结里——整个团队在打开 Slack 之前就已经知道发生了什么,无需任何人写一封复盘邮件。
如果同一个队列在故障前已经缓慢积压了好几天——是慢慢泄漏而不是突然崩塌——队列历史就会展示出来:每 5 分钟一个快照,保留 7 天,可以叠加消费者数量和发布速率进行分析。
如果你在生产环境中运行 RabbitMQ,并且曾经有过在故障期间盯着管理插件、不知道究竟哪里出了问题的经历,来试试吧。免费套餐涵盖你入门所需的一切,配置大约需要两分钟。
如果你想自己托管,核心部分是在 GitHub 上开源的。
Try it on your own broker.
Connect in under two minutes, wire your agent, and ask it what's wrong.