主题
RocketMQ 监控指标与告警
一句话:RocketMQ 的告警要顺着 CommitLog 的写路径往下看——PageCache 和磁盘决定写延迟,线程池决定会不会拒绝,分发和消费进度决定业务什么时候真正拿到消息。
适用版本:RocketMQ 4.9 / 5.3 / 5.5。mqadmin 子命令和运行时字段在不同版本略有差异,指标名与默认值以对应版本官方文档和实际输出为准;阈值只是起点,按自己的基线校准。
相关:03-消息存储与持久化、04-高可用与主从复制、07-死信队列与消费重试、08-生产故障场景处置。
为什么需要
RocketMQ 的故障很少以「Broker 挂了」出现。更常见的是:PageCache 刷不动,发送 RT 先升高;线程池排满后开始拒绝,生产端超时重试;或者消息已经写进 CommitLog,但分发落后,消费者看到的是延迟。等业务报「消息收不到」,现场往往已经错过了磁盘和线程池这两个先兆。
监控要回答四个问题:还写得进去吗、写得快不快、消费者还追得上吗、主从还同步吗。四个问题的指标和处置动作不同。
核心机制
四个观察面
| 观察面 | 主要指标/字段 | 反映什么 | 恶化后的先兆 |
|---|---|---|---|
| 写入 | 发送 TPS、putLatency99/putLatency999、pageCacheLockTimeMills | 写入耗时和 PageCache 竞争 | 发送 RT 升高、超时 |
| 分发 | dispatchBehindBytes | CommitLog 到 ConsumeQueue 的异步分发进度 | 消息已落盘但消费端看不到 |
| 线程池 | sendThreadPoolQueueSize、putThreadPoolQueueSize、pullThreadPoolQueueSize 及对应 *Capacity | 请求排队深度 | 队列贴满、拒绝、重试 |
| 磁盘 | commitLogDiskRatio、commitLogDirCapacity、df -h | CommitLog 所在盘余量 | 75/85/90% 三档水位 |
| 消费 | consumerProgress 的 diff、消费 TPS、%RETRY%、%DLQ% | 消费是否跟上 | 堆积、重试、死信 |
| 主从 | slaveFallBehind、SYNC_MASTER 的 syncFlushTimeout=5000 | 主从同步是否落后 | 切换丢消息、发送失败 |
| 路由 | NameServer 9876 可用性、clusterList | 客户端能否拿到路由 | 新客户端无法发送 |
| 资源 | JVM GC、fd、网络 | 进程是否停顿 | 心跳、同步、消费线程超时 |
字段名以 getBrokerRuntimeInfo 和 brokerStatus 的实际输出为准;不同 4.x/5.x 小版本的键可能增减,采集端要容忍缺字段。
链一:PageCache/磁盘慢 → 写延迟 → 线程池拒绝 → 重试放大
RocketMQ 的 CommitLog 是顺序追加,写入先落 PageCache,再由刷盘线程落盘。PageCache 脏页回落跟不上时,写线程会被阻塞,pageCacheLockTimeMills 升高,putLatency99 跟着升高。
延迟传导到 sendThreadPoolQueueSize / putThreadPoolQueueSize:队列贴满后新请求被拒绝,生产端收到超时或拒绝,开始重试;重试继续打同一个 Broker,PageCache 更忙,形成正反馈。ASYNC_FLUSH 下宕机还会丢最后约 500ms 的写入;SYNC_FLUSH 延迟更高,但 RPO 更小。
链二:分发落后 → 消费延迟
消息写入 CommitLog 后,还要由分发线程异步构建 ConsumeQueue。dispatchBehindBytes 持续增长,说明消费端还看不到最新消息——即使 Broker 的写入 TPS 正常,业务侧也会感到延迟。磁盘慢、CommitLog 过大、单条消息过大都会拖慢分发。
分发落后和消费堆积要分开看:前者是 Broker 内部没处理完,后者是消费者没追上。看 consumerProgress 的 diff 时,先确认 dispatchBehindBytes 没有同步增长;否则扩消费者没有意义。
链三:消费堆积 → 重试 → 死信 → 业务补偿
消费失败会进入 %RETRY%<consumerGroup>,并发消费默认重试 16 次、累计约 4 小时 46 分后进入 %DLQ%<consumerGroup>。积压时重试消息和正常消息争抢队列,消费 TPS 进一步下降;DLQ 增长后,业务只能靠人工或补偿任务处理。
consumerProgress 的 diff 持续上涨、消费 TPS 正常,通常是容量不足;消费 TPS 很低,先查下游 RT、数据库锁和 Full GC;TPS 为 0 但实例在线,查消费线程是否被卡住或 Rebalance 未完成。
链四:主从落后 → 切换丢消息
ASYNC_MASTER 下 Master 宕机,Slave 可能缺少最后一段写入;SYNC_MASTER 下 Master 要等 Slave 同步,Slave 不可用时发送会等满 syncFlushTimeout=5000 后失败,而不是静默降级成异步。slaveFallBehind 持续增长,说明复制已经落后,此时做切换要按 RPO 评估,不能假设 Slave 一定完整。
DLedger/Controller 模式用多数派选主,RTO 更短,但同样要监控同步落后和选主状态;经典主从默认不会自动切换,Master 挂了需要人工或外部编排。
链五:NameServer 不可用
客户端先向 NameServer 拿路由,再直连 Broker。NameServer 全挂后,已有客户端还能用本地缓存的路由继续发送,但新客户端拿不到路由,Topic 变更也不会生效。NameServer 之间不复制数据,靠每个 Broker 向所有 NameServer 注册;监控要逐个实例探活,不能只看一个地址。
最小上手
bash
sh bin/mqadmin clusterList -n 127.0.0.1:9876
sh bin/mqadmin brokerStatus -b 127.0.0.1:10911 -c DefaultCluster
sh bin/mqadmin getBrokerRuntimeInfo -b 127.0.0.1:10911
sh bin/mqadmin consumerProgress -n 127.0.0.1:9876 -g order-consumer
sh bin/mqadmin topicStatus -n 127.0.0.1:9876 -t order-topic
sh bin/mqadmin topicStatus -n 127.0.0.1:9876 -t '%DLQ%order-consumer'brokerStatus 在部分 5.x 版本直接返回运行时信息,getBrokerRuntimeInfo 在部分版本不存在;以 mqadmin help 和实际输出为准。rocketmq-dashboard(原 console)可以替代部分查询。
Prometheus 用 rocketmq-exporter,默认监听 5557:
bash
java -jar rocketmq-exporter.jar \
--rocketmq.config.namesrvAddr=127.0.0.1:9876
curl -s localhost:5557/metrics | grep -E \
'rocketmq_(broker_tps|broker_qps|producer_offset|consumer_offset|message_accumulation)'积压增长速率可以直接从 offset 算(官方 exporter README 的示例写法,阈值按业务调整):
promql
(sum(irate(rocketmq_producer_offset[1m])) by (topic))
- on(topic) group_right
(sum(irate(rocketmq_consumer_offset[1m])) by (group, topic)) > 100生产动作
以下阈值均为起点,按基线校准;告警要带 Broker、Topic、消费组三个维度。
| 告警 | 条件 | 级别 | 先做什么 |
|---|---|---|---|
| CommitLog 磁盘 | commitLogDiskRatio 大于 0.75 预警、0.85 严重、0.90 拒写 | P1/P0 | 清理过期文件、扩盘或迁 Topic;不删 commitlog |
| 写延迟 | putLatency99 超过基线 2 倍或持续大于 200ms | P1 | 查磁盘 IO、PageCache、单条大消息 |
| PageCache 竞争 | pageCacheLockTimeMills 持续高于基线 | P1 | 查刷盘压力和 vm.dirty_*,不要先重启 |
| 分发落后 | dispatchBehindBytes 持续增长或高于基线 | P1 | 查磁盘和 CommitLog 大小;消费扩容无效 |
| 线程池排队 | send/put/pullThreadPoolQueueSize 大于容量 80% 持续 1m;拒绝计数增长或日志出现拒绝 | P1 | 限流生产或扩 Broker;加线程可能加重磁盘 |
| 消费堆积 | consumerProgress 的 diff 超过阈值且 10m 斜率向上 | P1/P2 | 扩消费者到 Queue 数,再查下游 |
| 重试/死信 | %RETRY% 或 %DLQ% 的 maxOffset 持续增长 | P1 | 修消费逻辑,按 DLQ 重投流程处理 |
| 主从落后 | slaveFallBehind 持续增长;SYNC_MASTER 发送 RT 接近 5s | P1 | 查复制网络和 Slave 磁盘;不要盲目切换 |
| NameServer/Broker | 任一 NameServer 9876 不可用;clusterList 缺 Broker | P0 | 恢复实例;已有客户端可能仍靠缓存路由 |
| GC | Full GC 停顿大于 1s 或老年代大于 80% | P1 | 调堆、查泄漏;GC 会拖慢分发和消费 |
先扩容/限流还是先重启:
- 磁盘到 85% 以上:先清理过期文件、扩盘或迁 Topic;90% 已经拒写,重启不会释放空间,手工删 CommitLog 还会破坏索引。
- PageCache 忙、写延迟高:查磁盘 IO 和刷盘配置;重启会让 PageCache 变冷,恢复期更慢。
- 线程池贴满:先限流生产或扩 Broker;单纯加线程会把压力推到磁盘,可能更糟。
- 消费堆积:先把消费者扩到 Queue 数(不能超过),再优化下游;重启消费者会触发 Rebalance,积压更重。
- 主从落后:RPO=0 时不能切到落后的 Slave;先修复制,或让
SYNC_MASTER继续失败而不是静默降级。
处置顺序:磁盘 → PageCache/写延迟 → 线程池 → 消费堆积 → DLQ。先保住写入路径,再处理业务补偿;不要在磁盘满时重启 Broker 期待自愈。
边界
RocketMQ 的优势是业务消息语义丰富:延迟/定时消息、事务消息、顺序消息、Tag/SQL 过滤、重试与 DLQ、LiteTopic;CommitLog 统一顺序写 + ConsumeQueue 轻量索引,适合万级 Topic 和业务事件。rocketmq-dashboard 和 mqadmin 让日常排障比较直接。
代价是 CommitLog 和 PageCache 强绑定:写入快依赖内存和顺序写,磁盘一慢就传导到线程池;分发是异步的,写入成功不等于可消费;主从/Controller、DLQ、重试都需要专门运维;并行度受 Queue 数约束,生态规模不如 Kafka。
扬长避短:用 RocketMQ 承载订单、支付、通知这类需要事务、延迟、顺序和过滤的业务消息;Queue 数按目标消费者数的 2 倍规划;核心链路用 SYNC_MASTER 或 DLedger/Controller 守住 RPO;监控以 Broker 写路径和消费组 diff 为主。超大吞吐的日志/流式管道交给 Kafka;低延迟点对点调用不要用消息队列硬扛。
讲清楚
RocketMQ 的告警顺着写路径看。CommitLog 先写 PageCache,脏页回落跟不上时
pageCacheLockTimeMills和putLatency99先升,接着sendThreadPoolQueueSize贴满、请求被拒,生产端重试又把压力打回来。写入成功不等于可消费,dispatchBehindBytes持续增长说明分发落后,这时扩消费者没用。消费侧看consumerProgress的 diff,堆积会进重试 Topic,约 4 小时 46 分后进 DLQ,业务补偿压力随之而来。主从看slaveFallBehind,异步复制下切换可能丢最后一段,同步复制下 Slave 不可用会直接发送失败。磁盘 75/85/90% 三档要提前处理,重启和手工删 CommitLog 都不解决问题。RocketMQ 强在事务、延迟、顺序和过滤,代价是 CommitLog/PageCache 运维和主从复杂度;超大流式吞吐交给 Kafka。