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 压测,七个坑里前五个都跟"我自己的配置"有关,真正服务器的问题反而好查。

我的压测检查清单:

  1. 命令行跑,别用 GUI 加压(HEAP 调大)
  2. 先找拐点 TPS,别盲目堆并发
  3. 服务器开 ServerAgent + 放行 4444
  4. CSV 路径用绝对路径
  5. 关键请求必加业务断言(别信 HTTP 200)
  6. 思考时间贴近真实
  7. 看报告盯 90% Line / Error% / Throughput

性能测试不是为了证明系统多强,而是为了知道它在哪里会倒。踩完这些坑,我现在看到"压了 1000 并发"这种话,第一反应是问一句:你压测机当时 CPU 多少?

(写于 2026 年夏,LiteMall 压测复盘)

posted @ 2026-07-09 14:56  C(5,3)  阅读(21)  评论(0)    收藏  举报