引言:性能优化的本质

上线前压测:TPS 轻松 5 万,延迟 5ms 以内。生产环境一跑:TPS 破千就开始 Full GC,延迟飙到 500ms,系统濒临崩溃…

性能问题的常见表现

  • ❌ TPS 上不去,CPU 使用率却不高
  • ❌ 频繁 Full GC,Stop-The-World 时间长
  • ❌ 消息发送延迟高,P99 延迟上秒
  • ❌ 磁盘 I/O 成瓶颈,写入速度慢

本文目标

  • ✅ 掌握 JVM 参数调优方法
  • ✅ 理解操作系统层面优化
  • ✅ 优化 Broker 核心参数
  • ✅ 建立性能调优思维模型

一、性能调优方法论

1.1 调优三板斧

1. 测量(Measure)  → 建立性能基线
2. 分析(Analyze)  → 定位性能瓶颈
3. 优化(Optimize) → 针对性调优

切忌盲目调优

  • ❌ 看到别人的配置就直接copy
  • ❌ 不测量就调参,凭感觉优化
  • ❌ 多个参数同时修改,无法定位效果

正确姿势

  • ✅ 建立性能基线(压测记录初始状态)
  • ✅ 单一变量法(一次只改一个参数)
  • ✅ 量化效果(对比优化前后的指标)

1.2 性能指标体系

维度核心指标目标值采集方式
吞吐量TPS(消息/秒)根据业务需求Prometheus
延迟P99 延迟(ms)< 50ms客户端埋点
资源CPU 使用率< 70%top
资源内存使用率< 80%free -h
资源磁盘 I/O 等待< 10%iostat
稳定性GC 频率YGC < 1次/秒jstat
稳定性Full GC 间隔> 1小时jstat

二、JVM 参数调优

2.1 内存配置

2.1.1 堆内存大小

推荐配置

# 小型集群(TPS < 5000)
-Xms8g -Xmx8g -Xmn4g

# 中型集群(TPS 5000-20000)
-Xms16g -Xmx16g -Xmn8g

# 大型集群(TPS > 20000)
-Xms32g -Xmx32g -Xmn16g

核心原则

  1. -Xms 和 -Xmx 设置相同:避免运行时扩容,减少 GC
  2. -Xmn 设置为堆内存的 40-50%:平衡 Young GC 和 Full GC
  3. 堆内存不要超过 32GB:超过后指针压缩失效,内存利用率下降

验证

# 查看 JVM 参数
jinfo <pid> | grep -E "Xms|Xmx|Xmn"

# 查看实际内存分配
jmap -heap <pid>

2.1.2 堆外内存(Direct Memory)

RocketMQ 使用 Direct Memory 实现零拷贝,需合理配置:

# 堆外内存大小(默认等于堆内存)
-XX:MaxDirectMemorySize=16g

# 推荐配置:堆内存 + 堆外内存 < 物理内存 * 0.75
# 例如:64GB 物理内存
# 堆内存:32GB
# 堆外内存:16GB
# PageCache:剩余 16GB

监控堆外内存

# 查看 Direct Memory 使用情况
jcmd <pid> VM.native_memory summary | grep -A 10 "Internal"

2.2 垃圾回收器选择

2.2.1 G1 GC(推荐)

适用场景

  • 堆内存 > 4GB
  • 追求低延迟(P99 < 50ms)
  • 可接受略微降低吞吐量

配置示例

# runbroker.sh
JAVA_OPT="${JAVA_OPT} -server -Xms16g -Xmx16g -Xmn8g"

# 使用 G1 GC
JAVA_OPT="${JAVA_OPT} -XX:+UseG1GC"
JAVA_OPT="${JAVA_OPT} -XX:MaxGCPauseMillis=200"        # GC 暂停目标时间
JAVA_OPT="${JAVA_OPT} -XX:G1ReservePercent=10"         # 预留空间比例
JAVA_OPT="${JAVA_OPT} -XX:InitiatingHeapOccupancyPercent=45"  # 触发并发标记的堆占用率

# Young 区大小控制
JAVA_OPT="${JAVA_OPT} -XX:+UnlockExperimentalVMOptions"
JAVA_OPT="${JAVA_OPT} -XX:G1NewSizePercent=30"         # Young 区最小占比
JAVA_OPT="${JAVA_OPT} -XX:G1MaxNewSizePercent=40"      # Young 区最大占比

# Region 大小(可选)
JAVA_OPT="${JAVA_OPT} -XX:G1HeapRegionSize=16m"        # 单个 Region 大小

# GC 日志
JAVA_OPT="${JAVA_OPT} -XX:+PrintGCDetails"
JAVA_OPT="${JAVA_OPT} -XX:+PrintGCDateStamps"
JAVA_OPT="${JAVA_OPT} -Xloggc:${HOME}/logs/gc_broker.log"

监控 G1 GC

# 实时监控 GC
jstat -gcutil <pid> 1000 10

# 分析 GC 日志
# 下载 GCViewer:https://github.com/chewiebug/GCViewer
java -jar gcviewer.jar gc_broker.log

2.2.2 CMS GC(旧版本使用)

适用场景

  • 老版本 RocketMQ(4.5 之前)
  • 追求吞吐量优先

配置示例

JAVA_OPT="${JAVA_OPT} -XX:+UseConcMarkSweepGC"         # 使用 CMS
JAVA_OPT="${JAVA_OPT} -XX:+UseCMSCompactAtFullCollection"  # Full GC 时压缩
JAVA_OPT="${JAVA_OPT} -XX:CMSInitiatingOccupancyFraction=70"  # Old 区 70% 触发 CMS
JAVA_OPT="${JAVA_OPT} -XX:+CMSParallelRemarkEnabled"   # 并行 Remark

⚠️ 建议:新版本优先使用 G1 GC,CMS 已被废弃。


2.3 GC 调优实战

案例 1:Young GC 频繁

现象

jstat -gcutil <pid> 1000
# S0     S1     E      O      M     YGC  YGCT    FGC  FGCT
# 0.00  50.12  75.34  30.45  95.67  500  2.345    3  0.567

# YGC 每秒 0.5 次,过于频繁

分析

  • Young 区太小,对象快速晋升到 Old 区
  • 导致 Full GC 频率升高

优化

# 增大 Young 区
-Xmn8g  → -Xmn12g

# 或调整比例
-XX:G1NewSizePercent=30  → -XX:G1NewSizePercent=40

案例 2:Full GC 频繁

现象

# FGC 频率:10次/小时
jstat -gccause <pid> 1000 10

分析思路

1. 检查堆内存是否过小
   → jmap -heap <pid>

2. 检查是否有内存泄漏
   → jmap -dump:format=b,file=heap.hprof <pid>
   → 使用 MAT 分析

3. 检查是否有大对象
   → -XX:+PrintGCDetails 查看晋升对象大小

4. 检查 Old 区触发比例
   → 调整 -XX:InitiatingHeapOccupancyPercent

优化方案

# 1. 增大堆内存
-Xms16g -Xmx16g  → -Xms32g -Xmx32g

# 2. 提前触发并发标记
-XX:InitiatingHeapOccupancyPercent=45  → -XX:InitiatingHeapOccupancyPercent=40

# 3. 减少不必要的对象创建(代码层面)

三、操作系统优化

3.1 内核参数调优

# /etc/sysctl.conf

# 1. 文件句柄数
fs.file-max = 2097152

# 2. 虚拟内存
vm.swappiness = 0              # 禁用 swap
vm.max_map_count = 655360      # 增加内存映射区域数量
vm.overcommit_memory = 1       # 允许内存过量分配

# 3. 网络优化
net.core.somaxconn = 2048                    # 监听队列长度
net.core.netdev_max_backlog = 2048           # 网卡接收队列
net.ipv4.tcp_max_syn_backlog = 2048          # SYN 队列长度
net.ipv4.tcp_tw_reuse = 1                    # 重用 TIME_WAIT 连接
net.ipv4.tcp_fin_timeout = 30                # FIN_WAIT2 超时时间

# 4. TCP 缓冲区
net.ipv4.tcp_rmem = 4096 87380 16777216      # 读缓冲区
net.ipv4.tcp_wmem = 4096 65536 16777216      # 写缓冲区

# 应用配置
sysctl -p

3.2 文件系统优化

3.2.1 挂载选项

# /etc/fstab
# 使用 noatime 选项,减少磁盘 I/O
/dev/sdb1  /data/rocketmq  ext4  defaults,noatime,nodiratime  0  0

# 重新挂载
mount -o remount /data/rocketmq

效果

  • 减少访问时间戳更新
  • 降低磁盘 I/O 开销
  • TPS 提升 5-10%

3.2.2 I/O 调度器

# 查看当前调度器
cat /sys/block/sdb/queue/scheduler
# [mq-deadline] kyber none

# 修改为 none(针对 SSD)
echo none > /sys/block/sdb/queue/scheduler

# 永久生效(添加到 /etc/rc.local)
echo 'echo none > /sys/block/sdb/queue/scheduler' >> /etc/rc.local

调度器选择

  • HDD 机械硬盘:mq-deadline(默认)
  • SSD 固态硬盘:none 或 kyber
  • NVMe SSD:none

3.3 关闭透明大页

# 查看透明大页状态
cat /sys/kernel/mm/transparent_hugepage/enabled
# [always] madvise never

# 临时关闭
echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag

# 永久关闭(添加到 /etc/rc.local)
cat >> /etc/rc.local <<EOF
echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag
EOF

原因

  • 透明大页可能导致内存碎片
  • 引发 stop-the-world 停顿
  • 影响延迟敏感的应用

四、Broker 参数调优

4.1 核心参数配置

# broker.conf

# ============ 刷盘策略 ============
# 刷盘方式:SYNC_FLUSH(同步刷盘) | ASYNC_FLUSH(异步刷盘)
flushDiskType=ASYNC_FLUSH

# 异步刷盘参数
flushCommitLogTimed=false                    # 定时刷盘(false=实时)
flushCommitLogLeastPages=4                   # 最少4页才刷盘(减少I/O)
flushCommitLogThoroughInterval=10000         # 10秒强制刷盘一次
flushConsumeQueueLeastPages=2
flushConsumeQueueThoroughInterval=60000      # 60秒强制刷盘索引

# ============ 主从同步 ============
# 主从复制方式:SYNC_MASTER(同步) | ASYNC_MASTER(异步)
brokerRole=ASYNC_MASTER

# 高可用传输
haTransferBatchSize=32768                    # 主从同步批量大小(32KB)
haSendHeartbeatInterval=5000                 # 心跳间隔(5秒)
haHousekeepingInterval=20000                 # 连接检测间隔(20秒)

# ============ 网络通信 ============
# 发送线程池
sendMessageThreadPoolNums=16                 # 发送消息线程数
sendThreadPoolQueueCapacity=10000            # 发送队列长度

# 拉取线程池
pullMessageThreadPoolNums=16
pullThreadPoolQueueCapacity=10000

# Netty 配置
serverSelectorThreads=3                      # Netty Selector 线程数
serverWorkerThreads=8                        # Netty Worker 线程数
serverChannelMaxIdleTimeSeconds=120          # 连接最大空闲时间

# ============ 存储配置 ============
# CommitLog 文件大小(默认 1GB)
mapedFileSizeCommitLog=1073741824

# ConsumeQueue 文件大小(默认 600万字节)
mapedFileSizeConsumeQueue=6000000

# 消息保留时间(小时)
fileReservedTime=72                          # 保留3天

# 删除文件时间点(凌晨4点)
deleteWhen=04

# 磁盘使用率超过多少触发清理(默认75%)
diskMaxUsedSpaceRatio=75

# ============ 性能优化 ============
# 预热内存映射
warmMapedFileEnable=true                     # 启用预热
flushLeastPagesWhenWarmMapedFile=4096        # 预热时刷盘页数

# 传输零拷贝
transferMsgByHeap=false                      # 使用零拷贝(Direct Memory)

# 开启快速失败(防止消息堆积)
osPageCacheBusyTimeOutMills=1000             # PageCache 繁忙超时(1秒)

# 消息索引
messageIndexEnable=true                      # 启用消息索引
messageIndexSafe=false                       # 索引安全模式(false=高性能)

# ============ 限流保护 ============
# 单次查询消息最大数量
maxMessageSize=4194304                       # 单条消息最大 4MB

4.2 参数调优实战

案例 1:提升写入吞吐量

目标:TPS 从 3 万提升到 5 万

优化方案

# 1. 采用异步刷盘
flushDiskType=ASYNC_FLUSH

# 2. 减少刷盘频率
flushCommitLogLeastPages=4   → flushCommitLogLeastPages=8

# 3. 增大发送线程池
sendMessageThreadPoolNums=16 → sendMessageThreadPoolNums=32

# 4. 启用零拷贝
transferMsgByHeap=false

# 5. 关闭消息索引(如不需要按 Key 查询)
messageIndexEnable=false

效果

  • TPS 提升 60%(3万 → 5万)
  • 延迟略微增加(5ms → 8ms)
  • CPU 使用率上升(50% → 65%)

案例 2:降低消息延迟

目标:P99 延迟从 50ms 降到 20ms

优化方案

# 1. 同步刷盘(牺牲吞吐量)
flushDiskType=SYNC_FLUSH

# 2. 实时刷盘
flushCommitLogTimed=false

# 3. 增加 Netty 线程
serverWorkerThreads=8 → serverWorkerThreads=16

# 4. 使用 SSD + RAID 10(硬件层面)

效果

  • P99 延迟降低 60%(50ms → 20ms)
  • TPS 下降 20%(5万 → 4万)
  • 磁盘 I/O 压力增大

五、性能测试与验证

5.1 压测工具

# 1. RocketMQ 自带压测工具
cd /opt/rocketmq/bin

# 生产者压测
sh benchmark.sh Producer \
  -n 127.0.0.1:9876 \
  -t test_topic \
  -s 1024 \           # 消息大小 1KB
  -c 10 \             # 生产者数量
  -w 100              # 每个生产者的线程数

# 消费者压测
sh benchmark.sh Consumer \
  -n 127.0.0.1:9876 \
  -t test_topic \
  -g test_group \
  -c 10 \             # 消费者数量
  -w 100              # 每个消费者的线程数

# 2. 自定义压测(Java代码)

5.2 性能基线建立

步骤

1. 使用默认配置压测
   → 记录 TPS、延迟、CPU、内存、磁盘 I/O

2. 单一变量优化
   → 修改一个参数
   → 重新压测
   → 对比效果

3. 逐步叠加优化
   → 保留有效优化
   → 放弃无效或负面优化

4. 记录最终配置
   → 形成标准化配置模板

压测报告模板

## 压测环境
- Broker: 3 台(16核 32GB 内存,2TB SSD)
- NameServer: 2 台
- 测试客户端: 10 台

## 压测场景
- 消息大小: 1KB
- 生产者: 100 线程
- 消费者: 100 线程
- 持续时间: 30 分钟

## 性能指标
- TPS: 50,000 条/秒
- P99 延迟: 25ms
- CPU 使用率: 60%
- 内存使用率: 70%
- 磁盘 I/O 等待: 5%

## 优化配置
- JVM: -Xms16g -Xmx16g -XX:+UseG1GC
- Broker: flushDiskType=ASYNC_FLUSH, sendMessageThreadPoolNums=32
- OS: vm.swappiness=0, I/O 调度器=none

六、监控调优效果

6.1 关键监控指标

JVM 监控

# GC 频率
jstat -gcutil <pid> 1000 10

# 期望值:
# Young GC: < 1次/秒
# Full GC: < 1次/小时
# GC 暂停时间: < 100ms

Broker 监控(Prometheus):

# TPS
rate(rocketmq_broker_put_nums[1m])

# 延迟
histogram_quantile(0.99, rocketmq_producer_latency_bucket)

# 消息堆积
rocketmq_consumer_lag

系统监控

# CPU
top

# 磁盘 I/O
iostat -x 1

# 网络
iftop

6.2 性能对比

优化前 vs 优化后

指标优化前优化后提升
TPS30,00050,000+67%
P99 延迟50ms25ms-50%
Young GC 频率2次/秒0.5次/秒-75%
Full GC 间隔30分钟2小时+300%
CPU 使用率50%60%+10%

七、常见问题

Q1:调优后性能反而下降?

可能原因

  1. 参数配置冲突(如堆内存过大导致 GC 时间过长)
  2. 过度优化(如线程池过大导致上下文切换频繁)
  3. 硬件瓶颈(如磁盘 I/O 已达上限)

解决方法

  • 回滚到基线配置
  • 单一变量法重新调优
  • 检查硬件监控指标

Q2:如何确定 JVM 堆内存大小?

经验公式

堆内存 = 并发连接数 × 单连接内存占用 × 2(安全系数)

例如:
- 1000 个 Producer 连接
- 每个连接占用 10MB 内存
- 堆内存 = 1000 × 10MB × 2 = 20GB

验证方法

# 堆内存分析
jmap -heap <pid>

# 找出大对象
jmap -histo:live <pid> | head -n 20

Q3:SSD 和 HDD 配置区别?

SSD 配置

flushDiskType=ASYNC_FLUSH
flushCommitLogLeastPages=8
osPageCacheBusyTimeOutMills=1000

HDD 配置

flushDiskType=ASYNC_FLUSH
flushCommitLogLeastPages=16       # 更大批量减少 I/O
osPageCacheBusyTimeOutMills=5000  # 更长超时

八、总结

调优优先级

1. 硬件层面(ROI 最高)
   → SSD > HDD
   → 增加内存
   → 万兆网卡

2. JVM 层面
   → 堆内存大小
   → GC 算法选择
   → GC 参数调整

3. 操作系统层面
   → 关闭 swap
   → I/O 调度器
   → 内核参数

4. Broker 参数层面
   → 刷盘策略
   → 线程池大小
   → 网络配置

核心配置模板

高吞吐量场景

# JVM
-Xms32g -Xmx32g -Xmn16g -XX:+UseG1GC

# Broker
flushDiskType=ASYNC_FLUSH
flushCommitLogLeastPages=8
sendMessageThreadPoolNums=32
transferMsgByHeap=false

低延迟场景

# JVM
-Xms16g -Xmx16g -Xmn8g -XX:+UseG1GC -XX:MaxGCPauseMillis=50

# Broker
flushDiskType=SYNC_FLUSH
serverWorkerThreads=16

调优检查清单

  • 建立性能基线
  • 配置 JVM 参数(堆内存、GC)
  • 优化操作系统(swap、I/O 调度器)
  • 调整 Broker 参数(刷盘、线程池)
  • 压测验证效果
  • 监控关键指标
  • 记录优化配置
  • 定期Review和调整

下一篇预告:《消息丢失排查 - 全链路追踪与问题定位》,我们将讲解如何通过全链路追踪,快速定位消息丢失问题。

本文关键词性能调优 JVM优化 参数配置 压测验证