RabbitMQ消息堆积排查与监控实战:从命令行到自动化脚本
1. 聊一聊消息堆积这件事先说个真实场景某天凌晨群里突然有人喊“消息不动了”你打开 RabbitMQ 管理页面一看Queue 里的 messages 数字跟心跳一样稳稳停在一万二消费端一个都不往下走。这时候你用什么命令、按什么顺序排查直接决定你是一小时解决还是一上午回不了家。我在实际踩坑过程中把整个排查和监控链路完整走了一遍从后台管理命令到运行时探测再到自动化采集最后沉淀出一套可以直接抄作业的方案。这篇文章不聊那些花里胡哨的监控面板就聊怎么用 RabbitMQ 自带的命令行工具和一点点脚本把消息堆积这个老问题彻底摁住。先说适用范围只要你的服务在用 RabbitMQ不管是单机还是集群不管是 3.8.x 还是 3.12.x这套思路都能直接复用。如果你是刚接手一套别人搭的 RabbitMQ对里面到底跑了哪些队列、谁在消费、堆积在哪一概不知那这篇文章刚好能帮你快速建立一套完整的排查监控套路。其实消息堆积这件事本质就三条没人消费、消费太慢、消息生产太快。但问题是光知道这三条没用你得能定位到具体是哪一个队列、哪一个消费者、哪一个 exchange 在搞事。而这恰恰是 RabbitMQ 命令行工具最擅长的事情。2. 先用手边现成的命令给 RabbitMQ 做个体检2.1 队列堆积一眼看穿list_queues 的隐藏用法网上讲rabbitmqctl list_queues name messages的教程一抓一大把但大部分人都忽略了一个关键点这条命令还有其他几个重要字段可以叠加使用比如messages_ready和messages_unacknowledged。我先解释一下这三个字段的区别因为这是排查的第一步也是最容易混淆的地方messages队列里当前的总消息数也就是管理页面上显示的那个数字。messages_ready已经准备好、随时可以推给消费者的消息数量。messages_unacknowledged已经推给消费者、但消费者还没确认ack的消息数量。举个实际例子我排查过一个积分服务队列数字显示 5000看着还好结果仔细一看messages_ready只有 100messages_unacknowledged却有 4900。这意味着消息早就发给消费者了但消费者一直不 ack。问题根本不在堆积而在消费者处理卡的半死或者网络分区把连接挂住了。所以我的建议是排查第一步永远用这个命令rabbitmqctl list_queues name messages messages_ready messages_unacknowledged再加上consumers字段看一眼每个队列的消费者数量。如果某个队列的messages涨得厉害但consumers是 0那不用想了这个队列没人消费。如果consumers不为 0 但messages_unacknowledged持续走高那问题出在消费者处理能力上。操作提示如果你管理的实例特别多还可以按 vhost 过滤rabbitmqctl list_queues -p /消息堆积队列 name messages messages_ready messages_unacknowledged注意-p参数后面是 vhost 名称默认是/如果自定义过 vhost一定要指定不然看到的是别的地方的数据。2.2 消费者到底去哪了list_consumers 的细节看完成员数量还不够还要看消费者的状态。消费者退订、连接断开、channel 被关闭这些都是消息堆积的常见元凶。rabbitmqctl list_consumers这个命令在实际情况中特别有用尤其是当你需要确认消费者是不是真的活着。它会列出每个消费者所在的队列、channel、消费方式ack 还是 no-ack、以及当前有没有活跃的消息在传输。我踩过一个大坑有一个服务部署了两份副本一份代码是旧版一份是新版新版代码里消费者初始化失败直接退出了导致队列只剩一份消费者在顶着。但是因为另一份副本没报错监控面板也显示在线所以一直没发现。直到我用了list_consumers一查才发现那个队列的 consumer 数量少了一半跟预期的 2 个不符。完整一点的命令rabbitmqctl list_consumers -p / vhost queue_name channel_pid consumer_tag ack_requiredack_required是 True 还是 False 也很重要。如果是 False说明消费者用的是 autoAckno-ack消息一发出去就从队列里删掉了这时候如果消费者端处理逻辑崩溃消息就彻底丢了而且队列里永远不会有堆积。这种问题表面上看起来“没有堆积”但实际上消息在悄悄丢比堆积更可怕。2.3 连接和通道的排查list_connections 与 list_channels连接数异常也是造成堆积的重要原因。比如某个服务频繁重连连接数忽高忽低或者某个连接卡住了既不消费也不释放占着资源。rabbitmqctl list_connections name peer_host state channels通过这个命令可以快速找出处于running之外状态的连接比如blocked、closing以及哪个客户端 IP 占据的连接数最多。如果一个消费者客户端开了几十个连接但实际在消费的队列却没几个说明客户端的连接池配置不太合理。list_channels则能进一步帮你定位到具体的 channel 上rabbitmqctl list_channels connection pid consumer_count messages_unacknowledgedconsumer_count是 0 的 channel 却占着连接这种不干活白占资源的连接就是排查重点。3. 命令不够用上 rabbitmqctl eval 直接摸到 Erlang 运行时3.1 什么时候需要用到 eval 系列命令如果上面那些命令查完你还是没搞清楚问题在哪那大概率问题已经不从属于业务层的 queue 和 consumer而是沉到底层 Erlang 运行时了。比如内存分配异常、ETS 表占用过高、某个进程死锁等等。这时候通用命令就使不上劲了需要直接上手摸运行时。rabbitmqctl eval可以在 RabbitMQ 节点上执行 Erlang 代码片段相当于你钻进 Erlang 虚拟机里去看内部状态。这个命令很强大但用起来也需要注意分寸——千万别乱执行写操作。我经常用的一条 eval 命令是查看所有队列的 Erlang 进程信息rabbitmqctl eval queue_info_all().这个返回结果比 list_queues 要详细得多能直接看到队列的后台进程是否是活的idle还是running、队列的messages、consumers、reductions、memory等所有内部状态。如果你养成了只看list_queues的习惯这条命令会让你打开新世界的大门。3.2 利用 eval 探测连接和通道内部状态另外一条很有用的 eval 命令是查看连接的状态机信息rabbitmqctl eval rabbit_networking:connection_info_all().这个能列出所有网络连接的完整内部信息包括连接状态、channel 数量、协议版本、客户端提供的属性等等。有一次我排查一个消费者“看起来在线、实际不消费”的诡异问题用管理页面看一切正常list_consumers也显示在消费但消息就是不走最后就是用 eval 查到连接处于{blocked, ...}状态而阻塞原因是因为磁盘可用空间触发了全局流控。再说一条我个人很喜欢的查看节点自身运行状态rabbitmqctl eval rabbit_system_metrics:generate().这个会输出内存分布、二进制堆大小、ETS 表占用等运行时关键指标。如果你怀疑 RabbitMQ 是给内存干爆了才导致的全部队列阻塞这条命令能一眼看出是谁在吃内存。根据我自己运维的经验RabbitMQ 的内存大户往往不是队列堆消息而是二进制引用binary references和消息的副本被长时间滞留的通道引用着导致 GC 回收不掉。不过需要注意的是eval 命令输出格式对不熟悉 Erlang 的人来说不太友好满屏的列表字典看着头大。建议分两批执行第一条先看自己关心的那条键值第二条再确认整体情况别一条命令输出几百行硬啃效率太低。4. 把命令串成自动化监控脚本4.1 设计思路和关键监控指标手动敲命令只能解决燃眉之急要想真正把消息堆积扼杀在摇篮里还是得让机器替你盯着。下面这套脚本方案的思路其实很简单定时循环执行上面那些命令行工具把输出解析成结构化数据然后跟阈值做比对触发报警就通过企业微信/钉钉/飞书机器人推消息。我建议监控这几个核心指标它们基本覆盖了 90% 的堆积场景指标命令来源报警阈值建议队列消息总数list_queues messages各队列自定义比如持续 5 分钟超 10000未确认消息数list_queues messages_unacknowledged持续超过 100 就查消费者消费者数量为 0 的队列list_queues consumers有消息但消费者为 0直接报警连接状态异常list_connections state出现非 running 状态且持续 10 分钟节点内存水位rabbitmq-diagnostics memory超过总内存 40% 预警50% 告警注意口径监控要抓的是“持续一段时间”而不是瞬时快照。瞬时突然跳到高水位可能是正常业务峰值但持续 5 分钟还没降下来那大概率是真出事了。所以脚本里必须要做“连续 N 次超过阈值才算告警”的防抖逻辑。4.2 一个可以直接抄走的 Bash 监控脚本下面这个脚本是我现在还在用的简化版直接用 Bash 写的依赖只有 rabbitmqctl 和 curl任何一台能连上 RabbitMQ 管理节点的 Linux 机器都能跑。#!/bin/bash # RabbitMQ 堆积监控脚本 v1.0 # 依赖: rabbitmqctl, curl # 用法: 放入 crontab 每分钟执行一次 RABBITMQCTL/usr/sbin/rabbitmqctl VHOST/ ALERT_WEBHOOKhttps://qyapi.weixin.qq.com/cgi-bin/webhook/send?key你的机器人key ALERT_QUEUE_COUNT_THRESHOLD10000 ALERT_UNAACK_THRESHOLD500 WARN_FILE/tmp/rabbitmq_alert_state send_alert() { local content$1 curl -s $ALERT_WEBHOOK \ -H Content-Type: application/json \ -d {\msgtype\: \text\, \text\: {\content\: \$content\}} /dev/null } check_queue_metrics() { local queue_data queue_data$($RABBITMQCTL list_queues -p $VHOST name messages messages_ready messages_unacknowledged consumers --formatterjson 2/dev/null) # 使用 --formatterjson 解析避免文本格式空格问题 echo $queue_data | python3 -c import sys, json, urllib.request data json.load(sys.stdin) webhook $ALERT_WEBHOOK threshold int($ALERT_QUEUE_COUNT_THRESHOLD) unaack_threshold int($ALERT_UNAACK_THRESHOLD) alerts [] # 解析 Python 传过来的 queue 列表 for item in data: # RabbitMQ 3.8 JSON 格式类似 [{name: ..., messages: ..., ...}] if isinstance(item, list): for q in item: queue q.get(name, unknown) messages q.get(messages, 0) messages_ready q.get(messages_ready, 0) messages_unacknowledged q.get(messages_unacknowledged, 0) consumers q.get(consumers, 0) if messages threshold: alerts.append(f队列 {queue} 堆积: messages{messages} consumers{consumers}) if messages_unacknowledged unaack_threshold: alerts.append(f队列 {queue} 未确认过高: unack{messages_unacknowledged} consumers{consumers}) if messages_ready 0 and consumers 0: alerts.append(f队列 {queue} 有消息但消费者为0: ready{messages_ready}) if alerts: # 发送告警保留前20条防止刷屏 content [RabbitMQ监控] || .join(alerts[:20]) req urllib.request.Request( webhook, datajson.dumps({msgtype: text, text: {content: content}}).encode(utf-8), headers{Content-Type: application/json} ) urllib.request.urlopen(req, timeout5) } check_queue_metrics这个脚本用--formatterjson把 rabbitmqctl 输出切成 JSON 再交给 Python 去解析这么做有两个原因一是文本格式在不同 RabbitMQ 版本里字段排序不完全一致用脚本硬啃容易踩坑二是 JSON 解析代码量更少后续加字段也方便。使用之前记得先验证一下你手里的 rabbitmqctl 支不支持--formatter参数。3.8 版本没问题3.7 的话可能不支持需要换成-f json或者直接文本解析。5. 进阶用法rabbitmq-diagnostics 帮你发现隐患5.1 内存和磁盘最容易忽略的隐形杀手rabbitmq-diagnostics是跟 rabbitmqctl 并列的另一组工具。很多教程都不提但它对一些隐性问题的排查特别有用。最常用的两个命令是rabbitmq-diagnostics memory rabbitmq-diagnostics disk_spacememory会告诉你当前节点内存里哪些模块占了多少字节精确到code、binary、ets、connection_channels、connection_readers、connection_writers、queue_procs、queue_metrics这些细分类目。我实际排查过一次内存暴涨问题队列只有 2000 条消息但节点内存却到了 3 个 G。用rabbitmq-diagnostics memory一查发现connection_channels占了一半。原来是一个消费者客户端的 connection 池配置设成了 500每个连接的 channel 又开了几十个这些 channel 占用了一堆内存。再来看disk_spacerabbitmq-diagnostics disk_space输出两个值当前磁盘剩余可用空间和 RabbitMQ 认为的安全阈值。如果剩余空间已经低于阈值RabbitMQ 会触发全局流控所有队列的读写都会被卡住表现出来的症状就是“消费者在线但消息不动”非常迷惑人。5.2 检查端口监听和防火墙问题还有一个命令排查跨网络连接问题很管用rabbitmq-diagnostics listeners它会列出节点正在监听的所有端口、协议和接口地址。如果你发现消费者连接总是超时先跑这个命令确认 5672 确实在监听 0.0.0.0而不是只监听了 127.0.0.1。我碰上过云服务器安全组放行了 5672但 RabbitMQ 配置文件里listeners.tcp.default被设置成127.0.0.1导致外部访问死活连不上这问题不看 diagnose 根本发现不了。5.3 稳定连接检查ping 与 certificaterabbitmq-diagnostics ping可以快速确认节点是否存活并正常响应。我之前写过一些自动化脚本都会在脚本开头先跑ping如果返回Ping succeeded再继续执行后续命令否则直接报警“RabbitMQ 节点无响应”避免后续几十条命令全部报错刷屏。如果是走了 TLS 连接的场景rabbitmq-diagnostics certificate可以查看节点证书信息排查证书过期导致的连接失败问题也很方便。不过这个命令依赖 Erlang 的ssl模块有些精简版安装可能不支持用之前先确认下。6. 常见问题速查我踩过的那几个坑以下这些问题是我这两年排查 RabbitMQ 消息堆积问题时遇到频率最高的单独列出来做一份速查表现象优先执行命令可能原因解决思路队列消息数暴涨消费者在线但不消费list_queues name messages messages_ready messages_unacknowledged消费者卡死未 ack或连接阻塞检查 unack 数量重启消费者或修复业务逻辑队列有消息但 consumers0list_consumers消费者程序退订或启动失败检查客户端日志确认消费者初始化是否完成整个服务所有队列都堆积rabbitmq-diagnostics memorydisk_space内存或磁盘触发全局流控扩容资源关闭不用的连接清理消息连接状态是 blockedlist_connections state触发连接级或节点级流控查内存和磁盘水位恢复后再观察用户反馈消息丢失但队列没堆积list_consumers看 ack_required消费者用了 autoAck 且处理失败改成手动 ack并在确认业务处理成功后再回执脚本执行命令超时rabbitmqctl list_queues --formatterjson队列数量过大或节点负载过高分 vhost 采集或者增加超时时间6.1 未确认消息堆积的坑这绝对是我排过最多的一种情况。业务方把消费逻辑里某个第三方接口调用超时时间设成了 5 分钟导致一个消息要卡 5 分钟才 ack。高峰期每秒进来 30 条消息每条都卡 5 分钟未确认数直接飙到 9000。而队列显示的“总消息数”因为有 unack 占着额度看起来并没有特别离谱很容易让人忽略。这种坑的排查要点就是一定要看messages_unacknowledged。只要这个数值持续上升哪怕messages总数不大也说明消费者处理链路里有慢操作阻塞了 ack。6.2 多 vhost 环境下的命令坑命令忘记加-p参数指定 vhost 的话会只读取默认 vhost/的数据。如果你业务用的 vhost 叫order_system你盯着默认 vhost 的队列看半天啥都查不出来白白浪费时间。所有list_queues、list_consumers、list_channels命令养成习惯第一时间带上-p。6.3 Bash 脚本解析 rabbitmqctl 文本输出的坑RabbitMQ 的文本格式输出在队列名字含空格或者特殊符号时你会得到一个非常痛苦的结果字段全对不齐awk 解析直接裂开。尤其是那些带了 UTF-8 中文名称的队列文本模式下解析维护成本高到想骂人。所以我是真的建议直接用--formatterjson省心太多。如果你的 RabbitMQ 版本实在太老不支持 JSON 输出那就准备用 Erlang term output 加rabbitmqctl eval来拿内部数据也别硬用文本解析。6.4 管理插件没启用的坑rabbitmqctl的很多高级命令不依赖管理插件但如果你搞的是更细粒度的指标采集依赖了rabbitmq-management-agent而插件没启用命令就会报错。我踩过最典型的坑是rabbitmq-diagnostics memory在部分精简版环境里直接输出空结果最后去翻了插件列表才发现 management 插件压根没装。解决办法很简单rabbitmq-plugins enable rabbitmq_management启用后重启 RabbitMQ 服务等 5 到 10 秒再跑刚才的命令数据就正常了。7. 监控告警的防炸群技巧脚本写好了但如果你直接把所有异常都推送到告警群凌晨三点老板估计会被群消息炸醒然后来找你。所以告警策略也需要讲究点技巧。第一个经验是告警必须做“持续时间”判断。瞬时超阈值不一定是故障可能是发了一条超大消息导致的抖动。我在脚本里会维护一个状态文件记录每个队列首次超阈值的时间只有当连续 N 次比如 5 次每次间隔 1 分钟都超阈值才真正推送告警。这样能把大量无效告警过滤掉。第二个经验是告警消息里必须带上上下文信息。光说“队列 XX 堆积了”一点用没有要说“队列 order.paid.timeout 堆积messages15230consumers1unack9800建议检查消费者处理耗时”。看到消息的人立刻就能判断处理方向不需要再登录服务器敲命令做二次排查。第三个经验是告警和恢复通知要配套。问题恢复之后也要推送一条“队列 order.paid.timeout 已恢复当前消息数 321”这样值班的人才知道事情结束了不用反复确认。我是在脚本里加了状态比对如果上一个周期是告警状态、当前周期正常就触发一个恢复通知。8. 我实际用下来的几个小体会上面步骤走完基本上 RabbitMQ 消息堆积问题都能定位个七七八八。最后再分享几个我个人实操得来的小建议不算什么高深理论但都是真实项目里换来的经验。第一个建议producer 侧一定做消息确认和重发机制。RabbitMQ 的 publisher confirm 机制一定要开不然生产者以为发成功了实际上 Broker 端拒绝了消息直接静默丢失等消费者那边发现数据对不上排查链路就长了。第二个建议消费者侧的日志里一定要打印消息 ID 或者业务主键。很多堆积问题最后都要靠业务日志来还原时间线没有消息 ID 的日志排查效率至少砍半。我见过太多消费端代码直接logger.info(received msg)连个消息内容都不打出了问题完全不知道那批消息从哪来的。第三个建议RabbitMQ 监控脚本不要只跑在管理机上最好能和节点同网络减少网络延迟对采集的干扰。还有就是采集数据落库的话注意时间戳统一用节点本地时间或者统一转成 UTC不然排查跨节点问题的时候时间轴对不上非常痛苦。第四个建议每次改完队列参数、消费者代码、连接池配置之后至少观察 24 小时。因为很多问题不是立即出现的而是随着消息量增长逐渐暴露。比如连接池配太大空闲时看不出来高峰期才会把节点内存打满。最后一条也是最重要的一条给你的每个队列都想好“无人消费”的兜底策略。RabbitMQ 的消息堆积不会自己消失要么配 TTL要么配死信队列要么定时任务扫一遍。别指望“以后再说”以后只会变成事故。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →