先问个问题:你上一次被一行消息队列指标搞到加班到深夜,是什么时候?
如果你负责过带 RabbitMQ 的系统,大概见过管理界面 Queue 页面里那两列数字:Ready 和 Unacked。看起来就两个数,但线上出问题的时候,问法五花八门——“为什么 backlog 一直涨?”“为什么消费者没退出但消息不动了?”“为什么一切都正常内存却炸了?”十有八九,你要先在这两个数字里找答案。
这篇文章准备把这件小事讲透:这两个指标到底对应消息的什么状态,怎么结合消费速率判断系统是否健康,以及我用它们排查过的一次真实事故。无论你是刚接触 RabbitMQ 的开发,还是要背监控指标的运维,都可以照着这套方法去排查自己的队列问题。
1. 先把概念掰开:Ready 和 Unacked 到底对应消息的哪种状态
1.1 一条消息在 RabbitMQ 里的完整旅程
先说结论:Ready 和 Unacked 是消息在同一时刻的两种“归属状态”,不是两个独立队列。
一条消息从生产者发出来到最终被删除,正常情况下会经历这么几个阶段:
- 生产者调用 basic.publish 把消息发给 RabbitMQ;
- 消息进入交换机(Exchange),路由到目标队列;
- 消息落到队列里,此时它处于Ready状态,表示“我已经在队列里了,随时可以被消费者取走”;
- 消费者订阅队列后,Broker 会把消息推给消费者,消息状态从 Ready 变成Unacked,表示“我已经交给某个消费者了,但消费者还没告诉我处理成功”;
- 消费者处理完后返回 basic.ack,RabbitMQ 才把这条消息真正删除;
- 如果消费者返回 basic.nack 或 basic.reject,并且设置 requeue=true,消息会重新回到 Ready;
- 如果消费者在处理完之前连接断开,RabbitMQ 会认为这条消息没有投递成功,把它重新标记为 Ready,等下一个消费者来取。
所以看管理界面的时候可以简单理解为:Ready 是排队等待被消费的消息,Unacked 是已经发出去但还没收到确认的消息。
我用一个快递类比帮助记忆:RabbitMQ 就像一个快递中转仓库,Ready 是仓库货架上等待派送的包裹,Unacked 是快递员已经拿在手里、正在派送但还没有收件人签收的包裹。只有签收(ack)了,这单才算真正结束。如果快递员突然失联(消费者连接断开),没签收的包裹会被退回仓库重新上架,也就是变成 Ready。
1.2 Ready、Unacked 和消费者处理能力的关系
这里有个关键的、很多人第一次接触时会绕晕的点:Unacked 高不代表 RabbitMQ 出问题了,它代表的是消费端的处理进度和确认行为。
RabbitMQ 默认会尽量把消息推给消费者,但如果消费者处理不过来,Broker 也不会无限推。它靠一个叫 QoS(Prefetch)的机制来控制“同一个消费者手上最多能有多少条未确认消息”。假设某个消费者设置的 prefetch=10,那么它最多同时持有 10 条 Unacked 消息;处理完一条确认一条,才会再从队列里拿新的。
所以正常情况下,一个队列的 Unacked 数量大致在“消费者数量 × prefetch”的范围内波动。如果某个队列有 5 个消费者,每个 prefetch=10,那 Unacked 在 0~50 之间来回变化都算正常。它不会无限涨,除非消费者线程卡死、不 ack、或者处理速度远跟不上投递速度。
Ready 数量则更多反映“生产速度和消费速度的差值”。如果生产速度一直大于消费速度,Ready 就会持续累积;如果消费速度跟得上,Ready 会保持在低位甚至接近 0。换句话说,这两个数字是消产速度的“水位计”,而不只是一个静态的堆积量。
提示:我见过不少人把队列里有消息等同于“系统不健康”。实际上,Ready 有点积压很正常,尤其是批量导入、高峰流量这种场景。真正需要警惕的不是积压本身,而是“持续增长且不回落”。
2. 用这两个数字判断系统健康:组合情况与经验阈值
2.1 几种典型状态组合,分别说明什么
我把比较常见的队列状态组合整理了一张表,这些情况都是我在排查时真的遇到过的。排查思路放在右边,方便直接照着做。
| Ready | Unacked | 判断方向 | 优先排查动作 |
|---|---|---|---|
| 一直在涨 | 低位或很低 | 消费能力跟不上生产速度,或者根本没有消费者 | 看消费者是否上线、消费逻辑是否报错、是否需要对消费者扩容 |
| 低位 | 一直高且不降 | 消费者拿到消息但没有及时 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 大概 8000,Unacked 稳定在 300 左右不掉,消费者进程也活得好好的,日志里没有任何异常。
这种状态最迷惑人。消费者没退出、没报错、队列还能收到新消息,看起来只是“处理得慢”,但如果你只看到这些,很容易把问题误判成“扩几个消费者就能解决”。
我当时先把消费者连接数、Channel 数、Prefetch 配置拉出来看。RabbitMQ 管理界面的 Queues 页面里有一个 Consumers 链接,点进去能看到每个消费者连接对应的 channel、prefetch 以及当前 unacked 数量。结果发现一个反常的地方:有 3 个旧连接一直显示 unacked=100,但 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 数量应该每几秒就有变化,因为消息处理完成后要 ack,broker 会再推新的消息。
第三步,抓线程栈。因为我们用的是 Java 客户端,直接 jstack 看那个消费者线程卡在了哪里。结果发现线程全部 BLOCKED 在一个 HttpClient 调用上。再往下看代码,发现消费逻辑里调用了一个外部接口,而这个接口没有设置超时时间。也就是说:线程池里的线程被远程调用全部占住了,每条消息都拿不到返回结果,自然就永远不会执行到 basicAck。
3.3 最终修复与复测结果
问题定位清楚之后,修复就很简单了:
- 给外部 HTTP 调用加了 3 秒超时(连接超时 + 读取超时);
- 对连接池大小做了限制,避免单个队列把整个消费端的线程池拖垮;
- 对失败消息改用手动 nack 并投递到死信队列,而不是无限重试;
- 把 prefetch 从默认的无限值改成了 20,避免单消费者无脑拉取大量消息。
修复之后观察:Unacked 数量很快从 300 降到 20 左右,Ready 也开始稳步下降,几个小时后就完全消完了。
这次排查最让我印象深刻的不是技术有多难,而是Unacked 是一个特别好的“假死检测器”。只要消费者线程还活着但处理逻辑卡死,Ready 不一定会快速变化,Unacked 一定会先暴露问题。因为在我们的配置下,Unacked = 已投递未确认的消息,它们卡住不动,说明消费逻辑大概率在某处阻塞了。
提示:排查 Unacked 不降的问题,优先看三件事——1. 消费者的线程栈是否卡在某个调用上;2. 代码里是否所有路径都调用了 ack/nack;3. 外部依赖的耗时和超时时间是否合理。
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)。设置 autoAck=true 以后,Broker 一投递消息就立刻标记为已确认,Ready 会变成 0,Unacked 也基本不会堆积,页面看起来特别健康。
但代价是:如果消费者进程在处理消息的中途崩溃,这条消息就永久丢失了。因为在 RabbitMQ 看来,消息已经“处理成功”,根本不会重新投递。我之前遇到过一个系统,后台统计永远对不上账,排查到最后才发现消费端为了省事开了 autoAck,消费者在处理一半的时候抛异常退出,消息全丢了。
所以在涉及交易、计费、对账这种场景,一定要用手动 ack:确认业务逻辑真正执行成功之后,再调用 basicAck。这不是什么高深技巧,但确实是我见过最多人因为图省事踩进去的坑。
5.2 误区二:只调 prefetch 分不清是“慢”还是“卡”
Prefetch(QoS)表示的是“单个消费者通道上最多允许存在的未确认消息数量”。它不是并发线程数,但很多人把它理解成并发度,以为调到 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 把数据攒起来。跑一段时间之后你会发现,判断系统健康真的不需要多复杂的工具,有了趋势数据,就已经赢了一半。