要突破JMeter的单机性能极限必须从内存管理和分布式架构同时入手。
一、为什么JMeter跑不上去?
JMeter是纯Java应用,所有请求、响应、断言、结果采集都在JVM堆里完成,所以短板来自:
内存不足:JVM 堆太小-OOM;堆太大-长 GC 停顿导致TPS抖动。
CPU打满:单机线程数超过内核处理能力,如并发 2000+ 线程时上下文切换成本很高。
非必要性能开销:GUI 界面、结果树监听器、复杂的断言/脚本会急剧占用内存和 CPU。
优化原则:先榨干单机性能,单机实在扛不住再上分布式。
二、内存优化让单台施压机物尽其用
1. 堆内存和GC调优
直接修改 jmeter/jmeter.bat 脚本的JVM启动参数,不用默认值。
bash
# 示例:Windows 下 jmeter.bat 中的设置 (Linux 类似)
set HEAP=-Xms4g -Xmx4g -XX:MaxMetaspaceSize=256m
set GC=-XX:+UseG1GC -XX:MaxGCPauseMillis=100 -XX:G1ReservePercent=20
要点:
-Xms和-Xmx必须相等,避免运行时堆扩容带来的性能抖动。
堆大小不要超过物理内存的 60%~80%,给操作系统和 JMeter 外的内存 (如线程栈、直接内存) 留余量。
生产级压测弃用CMS,改用 G1GC (-XX:+UseG1GC)。G1 在延迟可控性上远好于CMS,适合大堆。
调小 MaxGCPauseMillis(如 100ms),让G1尽量做短停顿回收,避免一次长暂停打乱TPS曲线。
2. 监听器是最大的内存使用模块
强制规则:负载执行时禁用一切图形监听器。
删除或禁用 View Results Tree、Graph Results、Aggregate Report 等。
如果一定要看实时聚合数据,只能用Simple Data Writer(只写文件)或后端监听器,如 InfluxDB + Grafana。
在 jmeter.properties 中全局关闭不必要的采样结果保存:
properties
jmeter.save.saveservice.output_format=csv
jmeter.save.saveservice.response_data=false
jmeter.save.saveservice.samplerData=false
jmeter.save.saveservice.response_headers=false
这能把 .jtl 文件的体积和内存写入消耗降低 90% 以上。
3. 脚本和变量精简
CSV Data Set Config是替代大量用户自定义变量的高效方式,不要用几百个User Defined Variables 铺满计划。
正则表达式提取器 尽量用边界一致,避免 (.*?) 贪婪模糊一致。
JSR223元件永远用Groovy语言,并勾选缓存编译脚本,性能是BeanShell的 10~20 倍。
禁用所有调试用的Debug Sampler和Debug PostProcessor。
4. 命令行非GUI方式
bash
jmeter -n -t test.jmx -l result.jtl -e -o /report
-e -o 生成报告的同时不会在内存中累积数据。如果不用报告生成,可以去掉,或者加 -f 强制包括。
三、分布式压测突破单机上限
当一台机器优化到极致(如4核8G的机器大约能跑 2000~3000 线程)仍不够时,用分布式架构横向扩展。
1. 架构原理
主控(Controller):只负责分发测试计划、收集汇总结果,不施压。
从机(Agent):多台机器执行真实的 HTTP/TCP 请求,受主控指挥。
所有从机运行同一版本 JMeter,同一版本 Java,并位于低延迟同一网段。
2. 从机配置(每台施压机)
① 修改 jmeter.properties:
properties
server.rmi.ssl.disable=true # 关闭 RMI SSL,避免证书开销
server.rmi.port=1099 # 统一 RMI 端口
server.rmi.localport=4000 # 可选,限制返回数据端口范围,方便防火墙
② 确定 RMI 主机名(在多网卡/云环境必配):
启动 jmeter-server 时指定 Java 属性:
bash
jmeter-server -Djava.rmi.server.hostname=192.168.1.101
或在 jmeter-server 脚本里设置 RMI_HOST_DEF。
③ 从机同样要做内存优化:单独编辑其启动脚本,分配一样的大堆。
④ 防火墙开放 1099 及本地端口范围。
3. 主控配置
修改 jmeter.properties:
properties
remote_hosts=192.168.1.101:1099,192.168.1.102:1099,192.168.1.103:1099
server.rmi.ssl.disable=true
client.rmi.localport=6000 # 控制主控本地端口,可选
mode=StrippedBatch # 结果回传方式,见下文
4. 启动命令
先在所有从机运行 jmeter-server(后台保持)。
主控执行:
bash
jmeter -n -t test.jmx -r -l result.jtl -e -o /report
-r 表示启动所有 remote_hosts 中定义的从机。-l 会将所有从机结果聚合到一个文件。
如果只想指定某些机器:
bash
jmeter -n -t test.jmx -R 192.168.1.101,192.168.1.102 -l result.jtl
5. 结果回传调优防止主控被撑爆
分布式短板常出现在主控收集结果。默认是同步批量方式,从机每批 100 个采样结果就发送,主控聚合写入磁盘,高吞吐时主控 CPU/磁盘 IO 暴增。
改用 StrippedBatch 或 StrippedAsynch,在 jmeter.properties 中设置:
properties
mode=StrippedBatch
num_sample_threshold=1000
time_threshold=60000
StrippedBatch:只保留少数重点字段(时间戳、延迟、响应码等),丢弃响应体等大字段再批量发送。
time_threshold=60000(60秒):最多等 60 秒就发送,避免缓冲积压。
如果从机数量多、TPS 很高,可进一步用StrippedAsynch,但要注意异步下结果顺序可能混乱,一般不影响统计。
6. 同步启动和时钟
所有从机收到主控指令后会同时启动所有线程,不用额外处理。因此 Ramp-Up 时间要乘以从机数量吗?不需要。每个从机独立执行计划中的 Ramp-Up,总并发等于各从机线程数之和。如果计划中线程数为 100,Ramp-Up 60s,3 台从机就是 300 线程在各自 60s 内爬升。
必须 NTP 时间同步!否则聚合报告中延迟计算完全错误。
四、极限调优
以下可在上线前核对:
JVM - 堆大小:单机堆内存 ≤ 物理内存的 75%,且 -Xms 和 -Xmx 相等。
JVM - GC 算法:强制使用 G1GC,并设置 MaxGCPauseMillis=100 左右。
测试计划 - 监听器:负载执行时保证零 GUI 监听器,禁用 View Results Tree 等。
测试计划 - 断言:仅保留重点接口的响应码断言,去除不必要的正则/JSON 断言。
分布式 - 网络:主从机需在同一个低延迟网段,延迟建议 <1ms,且经过 NTP 时间同步。
分布式 - 结果方式:设置为 StrippedBatch,配合 num_sample_threshold 和 time_threshold 减少主控压力。
从机 - JVM:每台从机独立做内存优化,JVM 版本和主控完全一致。
系统 - 文件句柄:执行 ulimit -n 65535(或更高),避免“Too many open files”错误。
系统 - 内核参数:针对高并发连接调优 tcp_tw_reuse、tcp_fin_timeout 等参数,加快端口回收。
五、当分布式还不够时
如果已用10 台以上从机且仍不达目的,可考虑:
将测试计划拆分成多个独立场景,分别用不同主控群执行。
引入容器化,在Kubernetes中快速扩缩压测节点,利用JMeter Docker镜像。