RabbitMQ作为广泛使用的消息中间件,在集群部署下一旦流量陡增或节点异常,往往会出现消息堆积、消费延迟甚至脑裂等问题。要保证系统稳定,就必须对集群做全链路指标采集,也就是从底层Erlang节点到上层业务队列,把每一环的运行数据都抓出来分析。很多团队只配置了基础告警,却忽略了镜像同步状态和文件描述符使用量,导致故障定位耗时极长。

集群监控的核心指标分层
全链路采集的第一步是理清RabbitMQ集群中到底有哪些指标值得抓。从系统层级看,Erlang虚拟机层面的指标最为基础,包括节点内存占用、进程数、ETS表数量以及GC次数。RabbitMQ是用Erlang写的,它的调度器和BEAM虚拟机直接决定了消息处理的吞吐上限,如果某个节点内存快被消息持久化占满,但只监控了操作系统内存,就会漏掉关键信号。
再往上是RabbitMQ自身的资源对象指标,例如虚拟主机级别的连接数、信道数,以及队列级别的消息总数、就绪消息数、未确认消息数。特别是镜像队列,在集群里会有主副本和同步副本,主队列所在的节点如果挂掉,就要看同步进度是否完整。这部分指标能直接反映业务堆积情况,是监控大盘里最该突出的部分。
最后是业务链路指标,比如某个交换机绑定的队列消费速率、消费者ACK延迟、发布消息的往返耗时。这类数据通常要结合应用侧埋点,但RabbitMQ自身也通过message_stats事件记录了发布、交付、ACK的计数。把这三层指标用统一的标签(如cluster、node、vhost、queue)关联起来,才算真正打通了全链路。
基于Prometheus的拉取式采集方案
目前最主流的做法是在每个RabbitMQ节点启用rabbitmq_prometheus插件,它会暴露一个HTTP接口供Prometheus定时拉取。这种方式属于被动拉取,对RabbitMQ自身侵入小,且Prometheus的标签模型非常契合集群多维度查询。启用插件后,指标会以OpenMetrics格式输出,包含erlang、rabbitmq_queue、rabbitmq_channel等前缀。
配置上只需要在rabbitmq.conf里增加一行plugins.rabbitmq_prometheus.metrics_bind = true,然后重启节点。之后在Prometheus的scrape_configs中把每个节点的9419端口加进去。相比早期用rabbitmqctl脚本定时执行再推送到Pushgateway,拉取式减少了中间脚本的维护成本,也不会因为脚本超时导致数据断点。
下面是一个最简的Prometheus任务配置示例,展示如何对三个节点做拉取:
scrape_configs:
- job_name: 'rabbitmq_cluster'
static_configs:
- targets:
- 'node1:9419'
- 'node2:9419'
- 'node3:9419'
metrics_path: /metrics
scrape_interval: 15s
不过要注意,Prometheus拉取的是当前瞬时值,像消息速率这类需要计算的指标,要在Grafana里用rate()函数处理。另外如果集群启用了TLS,插件的HTTPS端点也要配置证书,否则拉取会被拒。这种方案的缺点是当节点数超过五十个时,单次拉取指标量很大,可适当提高 scrape_interval 来减轻压力。
通过HTTP API做自定义全链路归集
除了Prometheus插件,RabbitMQ还提供了完整的HTTP API(默认端口15672),通过/api/nodes、/api/queues、/api/overview等路径能拿到结构化JSON。对于不想引入额外插件、或者需要把RabbitMQ数据并入自有监控系统的团队,可以写一个采集器定时调用API,把数据归一化后写到时序库。
例如用Python每隔十秒请求一次/api/queues/%2F获取默认虚拟主机下所有队列的状态,提取messages_ready、messages_unacknowledged、consumer_utilisation字段。这种做法灵活度高,能顺带把节点间网络分区状态(/api/nodes里的partitions字段)也采了。缺点是API返回体较大,高频率采集会消耗不少HTTP连接,需要做好缓存和异常重试。
以下示例展示如何用Python请求队列接口并提取关键指标:
import requests
from time import sleep
auth = ('guest', 'guest')
base = 'http://node1:15672/api/queues/%2F'
while True:
resp = requests.get(base, auth=auth, timeout=5)
data = resp.json()
for q in data:
name = q['name']
ready = q['messages_ready']
unack = q['messages_unacknowledged']
util = q.get('consumer_utilisation', 0)
print(f'{name} ready={ready} unack={unack} util={util}')
sleep(10)
在实际生产中,我们往往把API采集和Prometheus插件结合:插件负责标准指标大盘,API负责补充业务标签和做跨集群比对。同时要给采集器设置独立的限流账号,避免监控流量影响正常消息收发。只有把节点、队列、消费者三层数据连贯采集,才能在告警时直接定位到是哪个交换机下的哪条队列引发了雪崩。
监控盲区与告警策略设计
全链路采集最怕出现盲区,比如只监控了主节点而没看镜像队列的同步状态,某天主节点切换后,才发现新主队列其实是落后太多的非同步副本,造成消息丢失。因此采集项里必须包含slave_nodes和synchronised_slave_nodes的差值,一旦不同步副本存在且未恢复就触发告警。
另一类盲区是文件描述符与socket耗尽。RabbitMQ每个连接、每个队列都会占FD,Linux默认限制常不够用。通过/api/nodes里的fd_used与fd_total比值做阈值告警,能提前避免节点拒绝新连接。告警策略建议分三级:预测级(使用率超70%通知)、严重级(超90%电话呼叫)、阻断级(分区发生或镜像全掉线直接熔断发布)。
最后需要把采集到的指标和链路追踪关联。比如在消息头里放入trace_id,消费侧上报时带上它,这样从RabbitMQ的投递计数异常可以反查到具体业务调用。全链路指标采集不是装个 exporter 就结束,而是持续根据故障复盘补充字段,才能让集群真正透明可控。