Skip to content
Charles Shao
Go back

JMeter 压测实战:组件模型、线程组、CLI 执行与 HTML 报告

views

上一篇性能测试:指标、负载模型、结果解读与容量规划把「怎么想」讲透了——RT/QPS/并发怎么换算、开放 vs 闭合模型为什么决定成败、P99 与 Coordinated Omission 的陷阱、容量怎么规划。这一篇是它的动手篇:用最主流的开源压测工具 Apache JMeter,把那些原则真正落到一条能跑的压测计划上。

JMeter 是 Apache 基金会下的性能测试工具,Java 编写、图形化、开源免费,主要用于接口测试和性能测试。它上手快、协议全、插件生态成熟,是后端工程师做压测的「默认选项」。但它也有一堆一不小心就踩的坑——用 GUI 跑正式压测、监听器开着把内存吃爆、闭合线程组高估容量——这些恰恰是上一篇理论的照妖镜。本篇就带着理论来用工具。

一句话定位:JMeter = 测试计划树(组件分层作用)+ 线程组(控制并发与节奏)+ 非 GUI 命令行执行(出 HTML 报告)。GUI 只用来搭脚本和调试,正式压测一律 jmeter -n 无界面运行;面向公网/竞价的高并发场景,还要用插件把默认的闭合线程组掰成开放模型。

TL;DR

Table of contents

Open Table of contents

1. 下载安装与一条铁律

Apache JMeter 官网下载最新版本,解压后进入 bin/ 目录:图形界面用 jmeter(Windows 用 jmeter.bat),命令行压测用 jmeter -n ...。JMeter 依赖 Java,运行前确保装了 JDK(新版本需要 JDK 8+)。

先把最重要的一条纪律钉在最前面:

不要用 GUI 运行压力测试。 GUI 只用于压测计划的创建和调试;正式执行请一律用命令行:

jmeter -n -t <脚本.jmx> -l <结果文件.jtl> -e -o <HTML 报告目>

原因很简单:GUI 要实时渲染界面、监听器要在内存里累积每一条请求/响应,这些开销会让施压机把资源花在画图上而不是发压,轻则测不准、重则施压机自己先崩。这条纪律贯穿全篇。

2. JMeter 组件模型:一棵分层作用的树

理解 JMeter 的关键,是把它想成一棵测试计划树:你在 GUI 里右键「添加」的每个组件都挂在树的某个节点下,而组件的作用域由它挂载的层级决定——挂在线程组下,就对该线程组里所有请求生效;挂在某个请求下,就只对那个请求生效。

JMeter 组件模型:左侧是测试计划结构树(测试计划 → 线程组 → 配置元件/取样器/断言/定时器/监听器的层级嵌套),右侧列出九大组件的职责——线程组控制并发、取样器发请求、配置元件做辅助配置、前后置处理器在请求前后处理数据、定时器控制节奏、断言校验响应、逻辑控制器控制流程、监听器收集展示结果。底部提示组件分层作用、挂错层级是常见坑,并给出最小可用回路。

JMeter 是一棵测试计划树:组件分层作用,挂在哪一级就对该级及其子节点的所有请求生效。

九大组件的职责:

组件职责压测中的角色
线程组(Thread Group)管理请求的运行时间和并发数量性能测试必设,决定施压强度与节奏
取样器(Sampler)生成各协议请求(HTTP/JDBC/TCP…),编辑请求与传参真正「发出去」的那一下
配置元件(Config Element)辅助请求:请求默认值、信息头、CSV 数据源简化和统一请求配置
前置处理器(Pre-Processor)请求发出处理数据(动态改参)构造动态请求
后置处理器(Post-Processor)响应提取数据(JSON/正则提取器)把响应值传给下游请求(关联)
定时器(Timer)控制请求节奏:思考时间、吞吐量整形模拟真实用户停顿、控制到达率
断言(Assertion)校验响应是否符合预期(状态码/内容)判断请求「成功」的标准
逻辑控制器(Logic Controller)控制请求的执行顺序与分支循环编排复杂业务流程
监听器(Listener)收集/展示结果(结果树、Summary、聚合报告)看结果,但压测时要克制使用

分层作用的直觉:把「HTTP 信息头管理器」挂在线程组下,线程组里每个 HTTP 请求都会带上这些头;把它挂在某个具体请求下,就只有那个请求带。断言、定时器同理。理解了这一点,你就能读懂任何一份 .jmx,也不会再纳闷「为什么我加的断言没生效」。

3. 线程组与负载:三旋钮和 QPS 换算

线程组是压测的引擎,核心是三个旋钮:

线程组与负载:左侧是三个旋钮——Number of Threads(并发用户数,例 10)、Ramp-up Period(多久把线程全部拉起,例 5 秒)、Loop Count(每线程循环次数,例 100);右侧是 Ramp-up 时间线,活跃线程数在爬坡期从 0 线性升到 N,之后进入满并发平台期持续压;底部大公式 QPS ≈ Number of Threads × Loop Count ÷ Ramp-up Period(s),例 10×100÷5=200 QPS,并提示这只是理想值、真实吞吐受 RT 制约,且线程组是闭合模型会高估容量。

线程组三旋钮与 Ramp-up 时间线:并发是「拉起来」的,理想 QPS = Threads × Loops ÷ Ramp-up。

理想 QPS 的换算:

例如 10 线程 × 100 循环 ÷ 5 秒 = 200 QPS

关键提醒:这只是理想上界。真实吞吐会被被测系统的 RT 卡住——系统一慢,线程就卡在等响应上,实际发出的 QPS 达不到公式值。这正是上一篇 QPS = 并发 / RT 的另一面:线程组给的是「并发」这个输入,QPS 是系统 RT 决定的输出。想精确控制到达率,得靠 §7 的吞吐量整形。

4. 从零搭一条 HTTP 压测计划

把组件拼起来,看一条最小可用的压测计划怎么搭(都在「测试计划」上右键逐级「添加」)。

4.1 创建线程组

在「测试计划」上右键 →【添加】→【Threads (Users)】→【线程组】。设置三旋钮:先用小值(如 5 线程、Ramp-up 1s、Loop 2)方便调试,脚本调通后再在命令行用属性覆盖成正式压测参数(见 §6)。

4.2 HTTP 请求默认值(配置元件)

在线程组上右键 →【添加】→【配置元件】→【HTTP 请求默认值】。把协议、服务器地址、端口填在这里,之后所有 HTTP 请求就不用各自重复填——改环境(测试→预发)只改这一处。

4.3 HTTP 信息头管理器(配置元件)

右键 →【添加】→【配置元件】→【HTTP 信息头管理器】。传 JSON 就加一行 Content-Type: application/json;需要鉴权就加 Authorization。挂在线程组下,对组内所有请求生效。

4.4 HTTP 请求(取样器)

右键 →【添加】→【取样器】→【HTTP 请求】。填路径、方法(GET/POST)和 Body。这就是真正发出去的请求。

4.5 响应断言(断言)

在 HTTP 请求上右键 →【添加】→【断言】→【响应断言】。根据响应判断请求是否正常——最常见是判断响应代码为 200,也可以断言响应体包含某个关键字。没有断言的压测是危险的:接口返回 500 但 HTTP 层「成功」时,报告里成功率依旧漂亮,你却在压一个一直报错的接口。

4.6 监听器:结果树与 Summary Report

调试阶段点 GUI 的「运行」看结果树,确认逻辑正确即可——但别在 GUI 里下压测结论,正式数据来自 §6 的命令行。

5. CSV 数据驱动:让参数「活」起来

如果所有虚拟用户都用同一个 ID、同一个关键词请求,数据库的 Buffer Pool 会把同一条记录反复预热、缓存命中率虚高,测出来的性能比真实好一大截(上一篇「数据多样性」误区讲的就是这个)。

解法是 CSV Data Set Config:右键 →【添加】→【配置元件】→【CSV Data Set Config】,指定一个 CSV 文件和变量名,JMeter 会为每次请求(或每个线程)依次读取一行,用 ${变量名} 引用到请求里。

# users.csv
userId,keyword
10001,shoes
10002,laptop
10003,phone

HTTP 请求里写 /search?uid=${userId}&q=${keyword},每次请求就吃不同的参数。GET 拉列表、POST 造不同 Body,都靠它喂多样化数据。这样压出来的缓存命中分布、慢查询模式才贴近生产。

6. 执行测试计划:非 GUI 命令行

脚本在 GUI 里调通、保存成 .jmx 后,正式压测一律走命令行

JMeter 正确工作流:① GUI 编辑/调试 .jmx(小负载验证)→ ② 保存 plan.jmx → ③ CLI 非 GUI 执行(-n 无界面压测)→ ④ 产出 result.jtl 和 HTML 报告。中间给出完整命令 jmeter -n -t plan.jmx -l result.jtl -e -o report/ 及各参数含义,并展示 -g 从已有 jtl 二次生成报告;红色提示别用 GUI 跑压测。底部说明 GUI 只搭脚本和调试、-e -o 直接出报告、-g 二次生成、结果树只在调试期开否则 OOM。

GUI 只搭脚本和调试;正式压测 -n 无界面运行,-e -o 出 HTML 报告,-g 从已有结果二次生成。

6.1 一条命令跑完并出报告

jmeter -n -t testplan/RedisLock.jmx -l testplan/result/result.jtl -e -o testplan/webreport

参数说明:

6.2 用属性把参数外置(便于 CI)

把并发/时长写死在 .jmx 里不利于复用。可以在线程组里用 ${__P(threads,10)} 这类属性占位,命令行用 -J 覆盖:

jmeter -n -t bidding_mix.jmx -l result.jtl -e -o ./report \
       -Jthreads=500 -Jrampup=300 -Jduration=1800

这样同一份脚本在 CI 里就能参数化跑不同压力档位(可无缝接进 Jenkins 流水线做常态化性能回归)。

6.3 从已有结果二次生成报告(-g

如果压测时只落了 .jtl、没直接出报告,或想对同一批数据重新生成报告:

jmeter -g testplan/result/result.jtl -o testplan/webreport

这在分布式压测各节点各自落盘、事后合并的场景尤其有用。

6.4 读 HTML 报告

生成的 HTML 报告里,最该盯的几项:

7. 逼近开放模型:Concurrency Thread Group + Throughput Shaping Timer

这是 JMeter 用户最容易忽略、却直接决定压测可信度的一点。上一篇讲过:默认线程组是闭合模型(固定 N 个虚拟用户循环「发→等→再发」),系统一慢,虚拟用户就「体贴地」少发请求——自带背压,会系统性高估容量,测不出真实拐点。

面向公网、尤其是 RTB 竞价这类开放到达的流量,应该逼近开放模型(恒定到达率)。JMeter 靠 JMeter Plugins 里的两个组件:

# Throughput Shaping Timer 的一条曲线示例(起始RPS, 结束RPS, 持续秒数)
0     2000   300     # 5 分钟从 0 爬到 2000 RPS(预热)
2000  2000   1800    # 恒定 2000 RPS 压 30 分钟(稳态取数)

诚实的边界:即便配了整形器,JMeter 本质仍是线程驱动、阻塞采样,在极致尾延迟测量上不如原生开放模型工具(wrk2、Gatling、k6)——它们内建 Coordinated Omission 校正。所以选型上:JMeter 适合功能验证、协议丰富的中低压场景;要压 RTB bidder 的 P99.9、要 CO 校正,优先上 wrk2/k6。别用错了工具还怪数字不准。

8. 分布式压测:一台施压机不够就加机器

单台施压机有硬天花板:它自己的 CPU、网卡带宽、可用端口(ephemeral port)会先打满。这时你测出的「系统最大 QPS」其实是施压机的上限,不是被测系统的。

分布式压测:Controller(client,持有 .jmx、汇总结果,用 jmeter -R worker1,worker2 启动)向多台 Worker(jmeter-server)分发脚本并收集结果,每台 Worker 才是真正发压的施压节点,所有 Worker 一起把压力打向被测系统 SUT(bidder/API)。底部说明单机施压会被自身 CPU/网卡/端口先打满、分布式把负载摊到多台 worker、Controller 汇总结果本身也有开销超大规模建议各自落盘事后合并。

分布式压测:Controller 只分发脚本与聚合结果,多台 Worker(jmeter-server)才是真正的施压节点,一起压向被测系统。

用法概览:

  1. 每台施压机启动 jmeter-server(worker)。
  2. Controller(client)用 -R 指定 worker 列表启动:
jmeter -n -t plan.jmx -R worker1:1099,worker2:1099,worker3:1099 \
       -l result.jtl -e -o report/
  1. Controller 把脚本分发给各 worker,各 worker 独立发压,结果回传汇总。

注意:Controller 回传聚合本身也有开销,超大规模建议各 worker 各自落 .jtl、事后用 -g 合并出报告,避免回传成为瓶颈。判断「是不是施压机瓶颈」的简单方法:盯施压机的 CPU、网卡利用率和 TIME_WAIT 端口数——它们先饱和,就该加机器了(这也和连接与 I/O 模型里的端口/连接约束一脉相承)。

9. 小结

JMeter 的价值不在于点几下 GUI 出个数字,而在于用对方法把上一篇的理论落成可跑、可信、可复现的压测

想把「为什么这么测」的原理补齐(负载模型、P99 与 Coordinated Omission、容量规划、wrk/Gatling/k6 脚本对比),回到理论篇:性能测试:指标、负载模型、结果解读与容量规划。理论管「怎么想」,JMeter 管「怎么做」——两篇配套,才是一条完整的性能验证闭环。

参考


views
Share this post on:

Previous Post
JVM 深入(一)· 入门篇:内存架构与类加载 —— 从字节码到对象的一生
Next Post
性能测试:指标、负载模型、结果解读与容量规划