4、压测脚本编写:JMeter 线程组、取样器、监听器配置,参数化与断言实战

好,咱们直接进入正题。这一章是压测脚本的「动手环节」。前面讲了理论、讲了规划,现在要落地了。我个人习惯,写脚本之前先想清楚三件事:模拟多少用户?发什么请求?怎么判断结果对不对? JMeter 里对应的就是线程组、取样器和断言。咱们一个一个来拆。

4.1 线程组:压测的「发动机」

线程组,说白了就是定义「有多少虚拟用户在跑」。你想想看,没有线程组,脚本就是个空壳。

核心配置项:

  • 线程数: 模拟的用户数量。比如 100 个线程,就是 100 个虚拟用户。
  • Ramp-Up 时间: 多少秒内启动完所有线程。我建议设成跟线程数差不多,比如 100 线程设 100 秒,这样用户是慢慢上来的,不会把服务器瞬间冲垮。
  • 循环次数: 每个线程跑多少次脚本。勾选「永远」的话,需要手动停止。
我的经验: 做基准测试时,我习惯把循环次数设大(比如 1000 次),然后跑固定时间(比如 5 分钟)。这样数据更稳定,不会因为启动阶段的数据波动影响结果。

嗯,这里要注意:线程组里有个「调度器」配置。如果你想让脚本跑 10 分钟自动停,就在调度器里设「持续时间」为 600 秒。这个比手动点停止按钮靠谱多了。

4.2 取样器:发什么请求?

取样器是真正干活的东西。HTTP 请求、JDBC 请求、Java 请求……最常用的是 HTTP 请求取样器。

HTTP 请求配置要点:

  • 协议: http 或 https。注意 https 需要配证书,不然会报握手失败。
  • 服务器名称或 IP: 目标地址。别写 localhost,除非你压的是本机。
  • 方法: GET、POST、PUT、DELETE 等。压测接口用 POST 居多。
  • 路径: 接口的具体路径,比如 /api/v1/login。
  • 参数: 可以写在 URL 里,也可以写在 Body 里。我建议用 Body Data,结构清晰。

举个例子,一个登录接口的配置:

协议:https
服务器:api.example.com
方法:POST
路径:/api/v1/login
Body Data:
{
  "username": "testuser",
  "password": "123456"
}

我曾经遇到过一个问题:压测时发现所有请求都返回 400 错误。查了半天,原来是 Content-Type 没设成 application/json。JMeter 默认是 application/x-www-form-urlencoded,跟后端不匹配。所以记得在 HTTP 请求下面加一个 HTTP 信息头管理器,手动指定 Content-Type。

4.3 参数化:让数据「活」起来

如果所有用户都用同一个账号登录,那压测结果毫无意义。服务器可能把数据缓存了,或者把账号给封了。所以我们需要参数化——让每个虚拟用户用不同的数据。

常用参数化方式:

方式 适用场景 我的建议
CSV 数据文件 大量独立数据(如 10000 个账号) 最常用,推荐
用户自定义变量 少量固定数据(如服务器地址) 适合全局配置
函数助手 随机数据(如随机手机号) 临时用用可以,别依赖

CSV 数据文件配置步骤:

  1. 准备一个 CSV 文件,比如 users.csv,内容如下:
user1,pass1
user2,pass2
user3,pass3
  1. 在线程组上右键 → 添加 → 配置元件 → CSV 数据文件设置。
  2. 配置文件名、变量名(比如 username, password)、分隔符(逗号)。
  3. 在 HTTP 请求的 Body Data 里引用变量:
{
  "username": "${username}",
  "password": "${password}"
}
避坑指南: 我曾经因为 CSV 文件编码问题,导致中文用户名乱码。解决方案:用记事本另存为 UTF-8 编码,或者用 Notepad++ 转一下。另外,CSV 文件最后一行不要留空行,否则会多读一个空数据。

4.4 断言:怎么判断结果对不对?

压测跑完了,结果全是 200 OK,就代表系统没问题吗?不一定。可能接口返回了错误信息,但状态码还是 200。所以我们需要断言——验证响应内容是否符合预期。

常用断言:

  • 响应断言: 检查响应文本是否包含某个字符串。比如登录成功返回 "token",失败返回 "error"。
  • JSON 断言: 针对 JSON 格式的响应,检查某个字段的值。比如检查 $.code 是否等于 0。
  • 大小断言: 检查响应大小是否在某个范围内。可以用来检测是否返回了完整页面。

我个人最常用的是 JSON 断言。举个例子:

响应内容:
{
  "code": 0,
  "message": "success",
  "data": {
    "token": "abc123"
  }
}

JSON 断言配置:
Assert JSON Path expression: $.code
Expected value: 0

如果 code 不等于 0,断言就会失败,JMeter 会在结果里标记为红色。这样你一眼就能看出哪些请求有问题。

嗯,这里有个小技巧:断言失败时,JMeter 默认不会停止脚本。如果你想在断言失败后停止整个测试,可以在断言里勾选「如果断言失败,停止线程」或「停止测试」。不过要小心,生产环境压测时别乱用,否则一个失败就把整个压测停了,你半夜还得爬起来重启。

4.5 监听器:怎么看结果?

脚本写好了,跑起来了,怎么看结果?监听器就是干这个的。

常用监听器:

监听器名称 用途 我的建议
查看结果树 调试脚本时看单个请求的详情 调试时用,压测时关掉,否则吃内存
聚合报告 统计吞吐量、平均响应时间、错误率 压测必用,最核心的监听器
图形结果 可视化展示吞吐量变化趋势 适合做汇报,直观
后端监听器 将结果实时发送到 InfluxDB + Grafana 生产环境压测推荐,实时监控

我刚开始做压测时,喜欢开着「查看结果树」跑压测。结果跑了 10 分钟,JMeter 直接 OOM 了。为什么?因为查看结果树会把每个请求的响应内容都存到内存里,1000 个请求还好,10 万个请求直接爆炸。所以记住:调试时用查看结果树,压测时用聚合报告。

4.6 实战:一个完整的压测脚本

咱们把上面这些串起来,写一个完整的压测脚本。假设我们要压测一个登录接口:

  1. 线程组: 100 线程,Ramp-Up 100 秒,循环 10 次。
  2. CSV 数据文件: 读取 100 个账号密码。
  3. HTTP 请求: POST 到 /api/v1/login,Body 里引用 ${username} 和 ${password}。
  4. JSON 断言: 检查 $.code 是否等于 0。
  5. 聚合报告: 查看吞吐量、平均响应时间、错误率。

脚本结构如下:

测试计划
├── 线程组 (100线程, Ramp-Up 100s, 循环10次)
│   ├── CSV 数据文件设置 (users.csv)
│   ├── HTTP 请求默认值 (协议、服务器地址)
│   ├── HTTP 信息头管理器 (Content-Type: application/json)
│   ├── 登录接口 (HTTP 请求)
│   │   └── JSON 断言 ($.code = 0)
│   └── 聚合报告 (监听器)

跑完之后,聚合报告里会显示:

  • #样本: 1000 个请求(100 线程 × 10 次)
  • 平均响应时间: 比如 120ms
  • 吞吐量: 比如 800 req/s
  • 错误率: 比如 0.00%

如果错误率不是 0%,那就得去排查了。可能是账号密码不对,可能是接口返回了其他错误码。这时候可以临时打开「查看结果树」,看几个失败的请求到底返回了什么。

核心要点: 压测脚本不是写一次就完事的。我每次压测前都会先跑 1 分钟调试,确认断言通过、数据正确,再跑正式压测。这叫「冒烟测试」,能帮你省下大量排查问题的时间。

4.7 本章知识体系

下面这张图,帮你把本章的核心逻辑串起来:

压测脚本核心结构 线程组 模拟用户数 & 启动策略 配置元件 CSV参数化 / 默认值 取样器 HTTP请求 / JDBC请求 断言 JSON断言 / 响应断言 监听器 聚合报告 / 图形结果 结果分析 吞吐量 / 响应时间 / 错误率 执行顺序:线程组 → 配置元件 → 取样器 → 断言 → 监听器 → 结果分析

这张图展示了压测脚本的完整执行流程。从线程组开始,经过配置元件、取样器,再到断言和监听器,最后输出结果分析。每一步都有它的作用,缺一不可。

好了,这一章的内容就到这儿。脚本写好了,下一件事就是跑起来看数据。记住:脚本是工具,数据才是答案。别在脚本上花太多时间纠结,跑起来再说。