恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
RabbitMQ队列监控:一文读懂Ready与Unacked指标,定位消费积压
首页
资讯中心
/
RabbitMQ队列监控:一文读懂Ready与Unacked指标,定位消费积压
RabbitMQ队列监控:一文读懂Ready与Unacked指标,定位消费积压
发布时间:2026/9/24 23:09:18
先问个问题你上一次被一行消息队列指标搞到加班到深夜是什么时候如果你负责过带 RabbitMQ 的系统大概见过管理界面 Queue 页面里那两列数字Ready 和 Unacked。看起来就两个数但线上出问题的时候问法五花八门——“为什么 backlog 一直涨”“为什么消费者没退出但消息不动了”“为什么一切都正常内存却炸了”十有八九你要先在这两个数字里找答案。这篇文章准备把这件小事讲透这两个指标到底对应消息的什么状态怎么结合消费速率判断系统是否健康以及我用它们排查过的一次真实事故。无论你是刚接触 RabbitMQ 的开发还是要背监控指标的运维都可以照着这套方法去排查自己的队列问题。1. 先把概念掰开Ready 和 Unacked 到底对应消息的哪种状态1.1 一条消息在 RabbitMQ 里的完整旅程先说结论Ready 和 Unacked 是消息在同一时刻的两种“归属状态”不是两个独立队列。一条消息从生产者发出来到最终被删除正常情况下会经历这么几个阶段生产者调用 basic.publish 把消息发给 RabbitMQ消息进入交换机Exchange路由到目标队列消息落到队列里此时它处于Ready状态表示“我已经在队列里了随时可以被消费者取走”消费者订阅队列后Broker 会把消息推给消费者消息状态从 Ready 变成Unacked表示“我已经交给某个消费者了但消费者还没告诉我处理成功”消费者处理完后返回 basic.ackRabbitMQ 才把这条消息真正删除如果消费者返回 basic.nack 或 basic.reject并且设置 requeuetrue消息会重新回到 Ready如果消费者在处理完之前连接断开RabbitMQ 会认为这条消息没有投递成功把它重新标记为 Ready等下一个消费者来取。所以看管理界面的时候可以简单理解为Ready 是排队等待被消费的消息Unacked 是已经发出去但还没收到确认的消息。我用一个快递类比帮助记忆RabbitMQ 就像一个快递中转仓库Ready 是仓库货架上等待派送的包裹Unacked 是快递员已经拿在手里、正在派送但还没有收件人签收的包裹。只有签收ack了这单才算真正结束。如果快递员突然失联消费者连接断开没签收的包裹会被退回仓库重新上架也就是变成 Ready。1.2 Ready、Unacked 和消费者处理能力的关系这里有个关键的、很多人第一次接触时会绕晕的点Unacked 高不代表 RabbitMQ 出问题了它代表的是消费端的处理进度和确认行为。RabbitMQ 默认会尽量把消息推给消费者但如果消费者处理不过来Broker 也不会无限推。它靠一个叫 QoSPrefetch的机制来控制“同一个消费者手上最多能有多少条未确认消息”。假设某个消费者设置的 prefetch10那么它最多同时持有 10 条 Unacked 消息处理完一条确认一条才会再从队列里拿新的。所以正常情况下一个队列的 Unacked 数量大致在“消费者数量 × prefetch”的范围内波动。如果某个队列有 5 个消费者每个 prefetch10那 Unacked 在 0~50 之间来回变化都算正常。它不会无限涨除非消费者线程卡死、不 ack、或者处理速度远跟不上投递速度。Ready 数量则更多反映“生产速度和消费速度的差值”。如果生产速度一直大于消费速度Ready 就会持续累积如果消费速度跟得上Ready 会保持在低位甚至接近 0。换句话说这两个数字是消产速度的“水位计”而不只是一个静态的堆积量。提示我见过不少人把队列里有消息等同于“系统不健康”。实际上Ready 有点积压很正常尤其是批量导入、高峰流量这种场景。真正需要警惕的不是积压本身而是“持续增长且不回落”。2. 用这两个数字判断系统健康组合情况与经验阈值2.1 几种典型状态组合分别说明什么我把比较常见的队列状态组合整理了一张表这些情况都是我在排查时真的遇到过的。排查思路放在右边方便直接照着做。ReadyUnacked判断方向优先排查动作一直在涨低位或很低消费能力跟不上生产速度或者根本没有消费者看消费者是否上线、消费逻辑是否报错、是否需要对消费者扩容低位一直高且不降消费者拿到消息但没有及时 ack或在处理中点卡住优先看消费者代码有没有同步阻塞、线程池是否满、外部依赖是否超时Ready 高Unacked 也高消费端全链路卡住生产者还在持续发这是最需要紧急处理的组合先看消费者是否存活再看是否死循环或假死Ready 很低Unacked 也低健康状态消费速度跟得上无需处理保持监控即可Ready 周期波动Unacked 跟随波动正常业务潮汐比如定时任务触发观察变化趋势不需要干预这张表只是最粗的判断。同一个场景在不同业务下结论完全不同订单支付消息队列里积压 1000 条可能就属于事故了但短信通知队列积压 10 万条可能都不需要处理。所以判断健康的关键不是绝对值而是变化趋势。2.2 不能只看两个静态数字速率与趋势才是关键有一次我给一个团队排查问题对方说“订单队列 Ready 有 12 万了怎么办”。我第一反应是问昨天同一时间是多少上周呢是刚涨上来的还是一直存在的结果对方翻了一下监控才发现这个积压量已经持续一周了每天上午 9 点冲到 12 万下午 4 点又能降到接近 0。这说明什么说明生产者在高峰期产生的量就是比消费快但消费者的晚高峰能消化完。这种情况业务是完全可接受的甚至可以不做任何调整只需要确认消费者不会因为积压太久而处理过期数据就行。反过来如果 Ready 平时接近 0今天突然从 0 涨到 5000而且两小时都没回落这就要立刻查了。哪怕绝对值只是 5000相对变化已经说明消费者大概率出了故障。Unacked 也是同理。我更习惯关注的是Unacked 是否长时间维持在某个高位不降以及它和 Ready 的比例变化。比如队列总消息 10000其中 9000 都是 Unacked那说明几乎所有消费者手上的消息都卡住了这比 Ready 涨到 50000 更可怕。2.3 不同场景下的经验基线阈值这种东西不同业务不可能完全一致但可以给一个从实践中总结出来的起步基线大家根据自己业务再微调消息处理有评价系统的实时队列Ready 平时应该在个位数如果超过 100 并持续增长 5 分钟就要告警。定时任务或批量处理队列Ready 允许有积压但 Unacked 不能长期超过“消费者数 × prefetch × 2”。任何队列Unacked 如果超过 1000 且半小时不降基本可以判定消费端有异常不是正常波动。关键是先记录自己系统的正常基线。建议运维同事在监控面板里把这两个指标单独拿出来画趋势线持续观察一周以上你自然就知道自己的业务正常水位大概在哪里。3. 一次真实的 Unacked 暴涨排障记录3.1 事故现场消费者还活着Unacked 却不掉了有一次我们线上的支付回调处理队列出了问题。现象是后台管理界面里队列总消息数一直涨点进去看Ready 大概 8000Unacked 稳定在 300 左右不掉消费者进程也活得好好的日志里没有任何异常。这种状态最迷惑人。消费者没退出、没报错、队列还能收到新消息看起来只是“处理得慢”但如果你只看到这些很容易把问题误判成“扩几个消费者就能解决”。我当时先把消费者连接数、Channel 数、Prefetch 配置拉出来看。RabbitMQ 管理界面的 Queues 页面里有一个 Consumers 链接点进去能看到每个消费者连接对应的 channel、prefetch 以及当前 unacked 数量。结果发现一个反常的地方有 3 个旧连接一直显示 unacked100但 Worker 线程已经不在处理任何任务了。3.2 排查过程从命令到代码最终定位到底层调用排查链路是这样的第一步用命令拉全局状态确认不是只有这一个队列有问题rabbitmqctl list_queues name messages_ready messages_unacknowledged consumers输出里只有支付回调队列异常其他队列都正常这说明问题跟 Broker 没太大关系要聚焦到支付回调队列的消费端。第二步看具体消费者的状态rabbitmqctl list_consumers queue name channel_pid prefetch_count unacked_count这一看就发现了问题有消费者连接一直显示“运行中”但 unacked_count 恒定在 30 条不动。正常情况下unacked 数量应该每几秒就有变化因为消息处理完成后要 ackbroker 会再推新的消息。第三步抓线程栈。因为我们用的是 Java 客户端直接 jstack 看那个消费者线程卡在了哪里。结果发现线程全部 BLOCKED 在一个 HttpClient 调用上。再往下看代码发现消费逻辑里调用了一个外部接口而这个接口没有设置超时时间。也就是说线程池里的线程被远程调用全部占住了每条消息都拿不到返回结果自然就永远不会执行到 basicAck。3.3 最终修复与复测结果问题定位清楚之后修复就很简单了给外部 HTTP 调用加了 3 秒超时连接超时 读取超时对连接池大小做了限制避免单个队列把整个消费端的线程池拖垮对失败消息改用手动 nack 并投递到死信队列而不是无限重试把 prefetch 从默认的无限值改成了 20避免单消费者无脑拉取大量消息。修复之后观察Unacked 数量很快从 300 降到 20 左右Ready 也开始稳步下降几个小时后就完全消完了。这次排查最让我印象深刻的不是技术有多难而是Unacked 是一个特别好的“假死检测器”。只要消费者线程还活着但处理逻辑卡死Ready 不一定会快速变化Unacked 一定会先暴露问题。因为在我们的配置下Unacked 已投递未确认的消息它们卡住不动说明消费逻辑大概率在某处阻塞了。提示排查 Unacked 不降的问题优先看三件事——1. 消费者的线程栈是否卡在某个调用上2. 代码里是否所有路径都调用了 ack/nack3. 外部依赖的耗时和超时时间是否合理。4. 配套的监控预警与常用命令让这两个数字自动报警4.1 rabbitmqctl 和 HTTP API 查 Ready/Unacked如果每次都靠打开网页手动刷新那效率太低了。实际工作中我会用命令行和 HTTP API 拿这两个指标。最常用的是 rabbitmqctl 命令直接列出所有队列的积压情况rabbitmqctl list_queues name messages_ready messages_unacknowledged consumers如果只想看某个队列可以用rabbitmqctl list_queues --queue payment.callback name messages_ready messages_unacknowledged consumers另一个方式是调用 RabbitMQ 的 HTTP API。管理界面本身就是基于这套 API 的所以只需要 curl 就能拿到 JSON 数据curl -s -u guest:guest http://127.0.0.1:15672/api/queues/%2F/payment.callback | jq {ready: .messages_ready, unacked: .messages_unacknowledged, consumers: .consumers}注意默认 vhost 是/在 URL 里要编码成%2F否则请求路径不对。返回 JSON 里的字段名就是 rabbitmqctl 里的那套方便脚本处理。这种方式很适合写脚本做定时巡检。比如我一个项目里就有个定时任务每 5 分钟拉一次messages_ready和messages_unacknowledged如果连续 3 次都超过阈值就自动往工作群里发告警。4.2 用 Prometheus 做指标监控与预警配置RabbitMQ 从 3.8 开始官方推荐用 Prometheus 插件做监控比之前的管理插件自带指标更轻量。启用方式只需要在配置里打开rabbitmq_prometheus然后暴露http://localhost:15692/metrics给 Prometheus 抓取。和 Ready/Unacked 直接相关的指标名字是rabbitmq_queue_messages_readyrabbitmq_queue_messages_unacknowledged它们都有queue、vhost等标签可以用 queue 名过滤。告警规则上我给一个参考思路持续 5 分钟不回落就报警groups: - name: rabbitmq_alerts rules: - alert: ReadyQueueGrowing expr: delta(rabbitmq_queue_messages_ready[5m]) 0 labels: severity: warning annotations: summary: 队列 {{ $labels.queue }} Ready 持续增长 - alert: UnackedQueueStuck expr: delta(rabbitmq_queue_messages_unacknowledged[5m]) 0 for: 10m labels: severity: critical annotations: summary: 队列 {{ $labels.queue }} Unacked 持续不降这里只是一个最简单的启动配置实际还需要根据业务调整阈值和 label 聚合方式。能用上 Prometheus 的团队我建议直接配起来因为看时间序列趋势比看数字快得多。4.3 顺带解决的热搜问题admin 账号权限和 Virtual Host在很多搜索记录里大家还会遇到另一个问题Docker 部署 RabbitMQ 后管理界面能打开但使用 admin 用户创建 Virtual Host 或者新建队列时页面直接报错或者按钮不可用。这个问题的根因和 Ready/Unacked 无关但它会影响你排查队列状态所以这里顺手提一下。官方镜像默认创建的 admin 用户可能只被赋予了 management 标签属于“管理员角色”但RabbitMQ 的权限分两层tag 是角色全局权限vhost 权限才是对着具体 virtual host 的读写配置权限。很多部署教程根本不会告诉你还要给用户分配 vhost 权限。解决办法是执行rabbitmqctl set_permissions -p / admin .* .* .*这个命令的意思是给 admin 用户在/这个 vhost 上配置、写、读三类权限全部放开。之后刷新管理界面新建 vhost、创建队列、绑定交换机就都正常了。这个坑很常见因为 Docker 部署方式下有些版本的默认配置并没有自动给 admin 分配对应权限导致大家以为“能登录就是有全部权限”。在排查队列指标之前先确认自己有没有权限创建临时队列做测试否则会白折腾半天。5. 常见误区和避坑经验我踩过的那些坑5.1 误区一autoAck 看着最健康其实数据在丢RabbitMQ 客户端在消费的时候可以选择自动确认autoAck和手动确认manual ack。设置 autoAcktrue 以后Broker 一投递消息就立刻标记为已确认Ready 会变成 0Unacked 也基本不会堆积页面看起来特别健康。但代价是如果消费者进程在处理消息的中途崩溃这条消息就永久丢失了。因为在 RabbitMQ 看来消息已经“处理成功”根本不会重新投递。我之前遇到过一个系统后台统计永远对不上账排查到最后才发现消费端为了省事开了 autoAck消费者在处理一半的时候抛异常退出消息全丢了。所以在涉及交易、计费、对账这种场景一定要用手动 ack确认业务逻辑真正执行成功之后再调用 basicAck。这不是什么高深技巧但确实是我见过最多人因为图省事踩进去的坑。5.2 误区二只调 prefetch 分不清是“慢”还是“卡”PrefetchQoS表示的是“单个消费者通道上最多允许存在的未确认消息数量”。它不是并发线程数但很多人把它理解成并发度以为调到 100、1000 就能让消费更快结果反而把问题搞复杂。我见过一个实际案例一个消费逻辑本身只需要 50ms但团队把 prefetch 调到了 2000猜测这样可以提高吞吐。结果下游数据库扛不住了消费处理时间从 50ms 飙升到几秒Unacked 堆到 2000 满额新消息全在 Ready 积压。最后还是靠压测把 prefetch 调回到 100 才解决。我的建议是prefetch 不是越大越好也不是越小越好。它应该结合单个消息处理耗时和目标吞吐量来设置。如果你希望单消费者达到每秒 50 条单条耗时 100ms那 prefetch 设置在 5~10 之间比较合理设置过大只会让 Broker 无脑把消息推给消费者造成不必要的内存和网络压力。5.3 误区三Unacked 高的时候先怀疑 Broker 而不是消费者这个误区太常见了。只要看到管理界面数字不对第一反应就是查 RabbitMQ 服务器、查内存、查磁盘折腾一圈发现服务器完全正常最后才想到消费者。实际上RabbitMQ 的 Unacked 指标反映的就是消费者侧的执行情况Broker 只是忠实地记录“哪些消息已经投递但没确认”。如果队列的 Unacked 居高不下优先怀疑三点消费者线程阻塞、外部依赖慢、代码漏了 ack。只有当管理界面出现 Node 状态红色、内存水位告警、磁盘告警的时候才需要先怀疑 Broker 本身。所以排查顺序很重要。我个人的顺序是先看消费者进程和日志再看 RabbitMQ 节点状态和内存最后才看网络和配置。5.4 关于 Quorum Queue 的补充高可靠场景下的更优选择最近几年的版本里RabbitMQ 一直在推 Quorum Queue。它和经典队列的最大区别是消息状态和投递状态通过 Raft 协议在多个节点之间复制能更好地避免节点故障时丢消息。Quorum Queue 对 Unacked 的处理也更严格因为每条消息的投递状态都要在多数节点上确认后才算数。如果你对消息可靠性要求很高建议优先考虑 Quorum Queue。它和经典队列在管理界面上的 Ready/Unacked 展示逻辑是一致的所以判断健康的思路完全通用不需要额外学习成本。提示Quorum Queue 在吞吐上通常略低于经典队列但对绝大多数业务场景完全够用。选择它不是因为性能更好而是因为一致性更强、运维更省心。具体选型还是要结合业务量和可用性要求来权衡。最后分享一个小技巧排查 RabbitMQ 队列问题用多了我现在已经形成了固定习惯每次打开管理界面不是先看哪个队列积压最多而是先看Unacked 有没有异常高位。因为 Ready 积压还有可能是业务高峰但 Unacked 长期不降基本就等于消费端出问题了。如果你所在团队还没有对 RabbitMQ 做监控建议先从这两个指标开始用 HTTP API 或者 Prometheus 把数据攒起来。跑一段时间之后你会发现判断系统健康真的不需要多复杂的工具有了趋势数据就已经赢了一半。