RocketMQ Broker 0 处理延迟 32 秒排障实录:从现象到根因到修复
生产环境 RocketMQ 4.8.0 broker-a:0 突发
RemotingTooMuchRequestException,putMessageEntireTimeMax飙到 32s,消息积压如山。本文完整记录排查思路、根因定位和最终修复方案,踩坑点全量给出。
1. 现象
凌晨告警群炸了,三连击:
ini
RemotingTooMuchRequestException: sendDefaultImpl call timeout
OFFSET_OVERFLOW_ONE
putMessageEntireTimeMax=32000ms(正常 < 100ms)
- 消费延迟从秒级跳到分钟级
- Producer 端大面积超时
- Grafana 上 broker-a:0 的
PutMessageDistributeTime直方图严重右偏
2. 环境背景
| 项目 | 配置 |
|---|---|
| RocketMQ 版本 | 4.8.0 |
| 部署模式 | 2m-2s-async |
| broker-a 主机 | 10.10.40.96(rockermq01) |
| NameServer | 10.10.40.96 ~ .99 |
| JVM 配置 | Xms8g/Xmx8g/Xmn4g,G1GC,InitiatingHeapOccupancyPercent=30 |
| DirectMemory | 15g |
| 日志路径 | ~/logs/rocketmqlogs/ |
3. 排查过程
3.1 第一反应:看 GC
bash
# 查看 GC 日志
jstat -gcutil <pid> 1000 10
# 如果开了 GC 日志文件
tail -200 ~/logs/rocketmqlogs/gc.log
发现 Full GC 间隔从正常的几十分钟缩短到 2-3 分钟,单次耗时 800ms+。G1GC 下 InitiatingHeapOccupancyPercent=30 太激进,8g 堆在消息量大的场景下频繁触发并发标记。
3.2 看线程栈
bash
jstack <pid> > /tmp/broker_stack.txt
关键发现:多个 PutMessageThread 线程 BLOCKED 在 CommitLog.putMessage 的锁上。说明写入已经串行化,这是延迟飙升的直接原因。
3.3 看磁盘 IO
bash
iostat -x 1 10
vbnet
Device: await svctm %util
sda 45.2 12.1 89.3
%util 接近 90%,await 45ms,磁盘 IO 是瓶颈之一。CommitLog 顺序写依赖 page cache,如果内存不够就会触发频繁 flush。
3.4 看操作系统层面
bash
# 看 broker 进程的内存映射
pmap -x <pid> | tail -5
# 看page cache占用
vmtouch /rocketmq/store/commitlog/
发现系统 free 内存不足 500MB,page cache 被其他进程挤压。RocketMQ 的 CommitLog 严重依赖 OS page cache 做顺序写加速,内存不够就直接退化到磁盘 IO。
3.5 OFFSET_OVERFLOW_ONE 的含义
这个不是根本原因,而是果------Consumer 拉取时 offset 比 CommitLog 最大 offset 大 1,通常是消息写入延迟导致 Consumer 轮询到尚未完成写入的 offset。修复写入延迟后自动消失。
4. 根因
三重叠加:
- JVM 堆偏小 + G1GC 触发阈值过低 → 频繁 GC 暂停写入线程
- OS 可用内存不足 → Page Cache 被压缩,CommitLog 顺序写退化为磁盘直写
- 磁盘 IO 接近饱和 → 写入延迟放大,PutMessageThread 锁竞争加剧
三者形成正反馈循环:GC 暂停 → 写入堆积 → IO 压力增大 → 写入更慢 → 堆积更多 → GC 更频繁
5. 修复方案
5.1 JVM 参数调优(立即生效)
bash
# 修改 runbroker.sh 中的 JAVA_OPT
JAVA_OPT="${JAVA_OPT} -server -Xms16g -Xmx16g -Xmn8g"
JAVA_OPT="${JAVA_OPT} -XX:+UseG1GC -XX:InitiatingHeapOccupancyPercent=45"
JAVA_OPT="${JAVA_OPT} -XX:MaxDirectMemorySize=15g"
JAVA_OPT="${JAVA_OPT} -XX:G1HeapRegionSize=16m"
JAVA_OPT="${JAVA_OPT} -XX:G1ReservePercent=20"
JAVA_OPT="${JAVA_OPT} -XX:ParallelGCThreads=8"
JAVA_OPT="${JAVA_OPT} -XX:ConcGCThreads=4"
关键变更:
- 堆从 8g → 16g,给消息索引更多内存空间
InitiatingHeapOccupancyPercent从 30 → 45,减少不必要的并发标记- 增加
G1HeapRegionSize=16m,减少大对象跨 Region G1ReservePercent=20防止晋升失败
5.2 系统层优化
bash
# 1. 释放被其他进程占用的内存(排查并清理无关服务)
# 2. 调整 vm.dirty_ratio 控制刷盘节奏
sysctl -w vm.dirty_ratio=10
sysctl -w vm.dirty_background_ratio=5
# 3. 确保 broker 进程的 oom_score 不被优先 kill
echo -1000 > /proc/<pid>/oom_score_adj
5.3 Broker 配置优化
properties
# 增加发送线程池大小
sendMessageThreadPoolNums=32
# 开启消息索引异步构建
messageIndexEnable=true
indexRebuildThreadNums=4
# 优化 flush 策略
flushCommitLogTimed=true
flushCommitLogInterval=500
5.4 中期方案:磁盘升级
- 将 SATA SSD 升级为 NVMe SSD
- 或将 CommitLog 与 ConsumeQueue 分盘存储,减少 IO 争抢
6. 修复效果
| 指标 | 修复前 | 修复后 |
|---|---|---|
| putMessageEntireTimeMax | 32000ms | < 50ms |
| Full GC 频率 | 每 2-3 分钟 | < 1次/小时 |
| 磁盘 %util | 89% | 35% |
| 消费延迟 | 分钟级 | 秒级 |
| OFFSET_OVERFLOW_ONE | 持续出现 | 消失 |
7. 踩坑总结
- G1GC 的 IHOP 不是越低越好:30% 在 8g 堆下意味着 2.4g 就触发标记,消息场景下太激进
- RocketMQ 的性能 = 内存 × 磁盘:Page Cache 被压缩时,再好的 JVM 参数也救不回来
- OFFSET_OVERFLOW_ONE 别急着调 Consumer offset:这是写入延迟的表象,修根因自动消失,手动调 offset 反而可能丢消息
- 2m-2s-async 的隐患:异步复制下 Master 压力大时 Slave 也会因复制延迟间接影响 Consumer 拉取
- 监控不能只看 Consumer 延迟:putMessageEntireTimeMax 才是 Broker 健康的核心指标
8. 监控告警建议
建议对以下指标设置告警:
yaml
# Prometheus 告警规则示例
- alert: RocketMQPutMessageSlow
expr: rocketmq_broker_put_message_entire_time_max > 1000
for: 1m
labels:
severity: critical
annotations:
summary: "Broker {{ $labels.instance }} 写入延迟超过 1s"
- alert: RocketMQGCPause
expr: increase(jvm_gc_pause_seconds_sum[5m]) > 10
for: 2m
labels:
severity: warning
annotations:
summary: "Broker {{ $labels.instance }} GC 累计暂停过长"
一句话总结:RocketMQ Broker 写入延迟飙升,先看 GC 和内存,再看 IO,三重叠加时别只改一处------JVM、OS、Broker 配置要一起调。
本文基于 RocketMQ 4.8.0 生产环境真实排障记录整理,转载请注明出处。