JMeter性能测试踩坑实录:别让你的压测机比服务器先跪
JMeter性能测试踩坑实录:别让你的压测机比服务器先跪
做性能测试最容易犯的错,是以为自己在压服务器,其实在压自己的笔记本。
大三下做 LiteMall 项目,导师一句"上线前压一压,看看能扛多少并发",我拍胸脯答应了。环境是 CentOS 虚拟机 + JMeter 5.6.3 + ServerAgent 监控。
结果第一次压测,TPS 死活上不去,我还以为是服务器太烂。折腾三天才发现——瓶颈在我自己的网关本(压测机)上。下面把踩过的坑一个个记下来,能帮你省不少时间。
坑一:用 GUI 模式跑压力,本机先爆了
新手最容易踩的坑:双击 ApacheJMeter.jar,在界面里填好线程数点"启动"。界面好看,但 GUI 模式本身就要吃掉大量内存和 CPU 来渲染波形图、刷新表格。
当我把线程数拉到 500,JMeter 自己的界面先卡死,日志里全是 java.lang.OutOfMemoryError。
正确姿势:写好的脚本(.jmx)一律用命令行跑。 GUI 只用来编辑和调试。
# 先给压测机加内存(bin/jmeter 顶部)
export HEAP="-Xms4g -Xmx4g -XX:MaxMetaspaceSize=512m"
# 无界面运行,-n 非GUI,-t 脚本,-l 结果,-e -o 生成HTML报告
jmeter -n \
-t LiteMall_OrderFlow.jmx \
-l result/order_$(date +%Y%m%d_%H%M).jtl \
-e -o report/order_latest/
跑完直接出一份 HTML 报告,图表比 GUI 里还全,而且压测机资源全用在发请求上。
坑二:并发数 ≠ TPS,被指标忽悠
一开始我的目标写得含糊:"压 1000 并发"。问题来了——1000 是线程数(虚拟用户),不是吞吐量。
线程数 = 同时活跃的请求数;TPS(每秒事务数)= 系统真正处理完的速度。一个请求 2 秒才返回,1000 线程顶多给你 500 TPS,再多加线程只会让响应时间更慢,TPS 反而掉。
关键认知:调并发数看的是"拐点",不是越大越好。
我后来用阶梯加压找系统拐点:
# 用 Stepping Thread Group 插件,或 Ultimate Thread Group
# 每 30 秒加 100 线程,直到 1000,观察 TPS 何时不再涨、响应时间开始飙升
# 那个"不再涨"的点,就是系统真实容量
LiteMall 在 400 并发时 TPS 到顶(约 230),之后加线程响应时间从 800ms 飙到 5s——400 才是它的甜点区,不是 1000。
坑三:ServerAgent 没起来,监控一片黑
光看 JMeter 自己报的 TPS 不够,你还得知道服务器当时 CPU、内存、磁盘 IO 到底什么状态。否则 TPS 掉了,你都不知道是 CPU 跑满还是数据库连接池耗尽。
ServerAgent 要单独在 CentOS 上启动,且默认端口 4444 必须放行:
# 服务器上
cd /opt/server-agent
chmod +x startAgent.sh
./startAgent.sh --tcp-port 4444 --udp-port 4444 &
# 防火墙放行(CentOS 7)
firewall-cmd --zone=public --add-port=4444/tcp --permanent
firewall-cmd --reload
然后在 JMeter 里加 jp@gc - PerfMon Metrics Collector 监听器,绑定服务器 IP:4444,勾选 CPU / Memory / Disks I/O。
第一次我没开防火墙,监听器一直显示"无数据",还以为是插件坏了,排查半天才发现是端口被挡。
坑四:CSV 参数化路径在分布式下翻车
压测下单接口肯定要用不同用户、不同商品,不能所有线程抢同一个数据。我用 CSV Data Set Config 做参数化:
# users.csv
user001,pass001
user002,pass002
user003,pass003
<CSVDataSet guiclass="TestBeanGUI" testClass="CSVDataSet">
<stringProp name="filename">users.csv</stringProp>
<stringProp name="variableNames">uname,pwd</stringProp>
<stringProp name="delimiter">,</stringProp>
<boolProp name="recycle">true</boolProp>
</CSVDataSet>
踩坑点:filename 写相对路径 users.csv 时,JMeter 是相对于"启动命令的当前目录"去找的,不是相对于 .jmx 文件。我在另一台机器上跑脚本,./users.csv 根本不存在,于是 200 个线程全用了空用户名,下单全失败。
改成绝对路径,或者把 CSV 放到和启动目录一致的位置才稳。
坑五:忘了加断言,压了个寂寞
最隐蔽的坑。请求返回 HTTP 200,JMeter 就高高兴兴标记为"成功"。但业务上可能返回的是 {"code":500,"msg":"库存不足"}——状态码是 200,业务是失败的。
不加拿么断言,你压出来的"高 TPS"全是假象。
<!-- 给关键请求加 JSON 断言 -->
<JSONPathAssertion>
<stringProp name="JSON_PATH">$.code</stringProp>
<stringProp name="EXPECTED_VALUE">0</stringProp> <!-- 我们约定 code=0 成功 -->
<boolProp name="JSONVALIDATION">true</boolProp>
<boolProp name="EXPECTEDVALUE">true</boolProp>
</JSONPathAssertion>
加上断言后,LiteMall 的真实成功率从"看着 100%"掉到 92%——那 8% 失败全是超卖导致的库存校验拦截。这才是值得关注的真实数据。
坑六:没设思考时间,压出"神仙数据"
不加任何延迟,200 个线程会以机器能发出的最大频率狂轰接口,中间零间隔。真实用户下单怎么会 0 间隔点按钮?这压出来的是理想峰值,不是真实负载。
用 Gaussian Random Timer 模拟真实思考:
# 高斯随机定时器
# 偏差(Deviation): 300 ms
# 固定延迟(Offset): 1000 ms
# 效果:每个请求之间随机停顿 700~1300 ms
加上思考时间后 TPS 自然下降,但数据贴近真实用户行为,得出的容量规划才靠谱。
坑七:聚合报告读错,误判性能
聚合报告 里几个数最容易看反:
| 指标 | 含义 | 我踩的误区 |
|---|---|---|
| 90% Line | 90% 请求快于该值 | 我当初只看 Average,结果平均 800ms,但 90% Line 已经 3s——长尾严重 |
| Throughput | 吞吐量(TPS) | 误当成"并发数" |
| Error % | 错误率 | 没加断言时永远是 0,毫无意义 |
| Std. Dev. | 响应时间标准差 | 越大说明波动越剧烈,体验越差 |
正确读法:盯 90% Line + Error% + Throughput 三件套,Average 只是参考。
总结:压测是门"怀疑自己"的活
回顾这次 LiteMall 压测,七个坑里前五个都跟"我自己的配置"有关,真正服务器的问题反而好查。
我的压测检查清单:
- 命令行跑,别用 GUI 加压(HEAP 调大)
- 先找拐点 TPS,别盲目堆并发
- 服务器开 ServerAgent + 放行 4444
- CSV 路径用绝对路径
- 关键请求必加业务断言(别信 HTTP 200)
- 加思考时间贴近真实
- 看报告盯 90% Line / Error% / Throughput
性能测试不是为了证明系统多强,而是为了知道它在哪里会倒。踩完这些坑,我现在看到"压了 1000 并发"这种话,第一反应是问一句:你压测机当时 CPU 多少?
(写于 2026 年夏,LiteMall 压测复盘)

浙公网安备 33010602011771号