JMeter 性能测试实战笔记
右键 HTTP 请求 → Add → Assertions → JSR223 Assertion// 校验 code 为 200 且 data 不为空AssertionResult.setFailureMessage("断言失败:code=${responsecode。
一、JMeter 基础概念
核心结论
JMeter 是 Apache 开源的性能测试工具,核心由线程组、采样器、监听器、配置元件、前置/后置处理器、断言六类组件构成。理解各组件的执行顺序和作用域是写出稳定测试脚本的基础。
1. 核心组件与执行顺序
JMeter 测试计划的执行顺序固定,理解这个顺序有助于正确放置各类组件。
| 组件类型 | 作用 | 典型示例 |
|---|---|---|
| 线程组 | 模拟并发用户,控制线程数/循环次数/Ramp-Up | Thread Group |
| 配置元件 | 提供请求所需的配置数据 | CSV Data Set Config、HTTP Cookie Manager |
| 前置处理器 | 请求发送前执行,准备动态数据 | JSR223 PreProcessor、User Parameters |
| 采样器 | 实际发送请求 | HTTP Request、JDBC Request |
| 后置处理器 | 响应返回后执行,提取数据 | JSON Extractor、正则表达式提取器 |
| 断言 | 验证响应是否符合预期 | Response Assertion、JSON Assertion |
| 监听器 | 收集并展示测试结果 | 聚合报告、查看结果树 |
2. 前置处理器 vs 后置处理器
前置和后置处理器是最容易混淆的两类组件,核心区别在于执行时机。
| 特性 | 前置处理器 | 后置处理器 |
|---|---|---|
| 执行时机 | 采样器请求发送之前 | 采样器收到响应之后 |
| 典型用途 | 动态生成参数、修改请求头、准备测试数据 | 提取响应数据、保存变量、调试 |
| 常见组件 | JSR223 PreProcessor、CSV Data Set Config | JSON 提取器、正则表达式提取器、Debug PostProcessor |
| 失败影响 | 失败会阻止采样器执行 | 失败不影响采样器本身结果 |
作用域规则: 附加到采样器则仅对该采样器生效;附加到线程组或逻辑控制器则对其下所有采样器生效。
典型流程示例:
CSV Data Set Config(前置)→ 读取用户名密码
JSR223 PreProcessor(前置)→ 生成加密参数
HTTP 请求(采样器)→ 发送登录请求
JSON 提取器(后置)→ 提取 Token 存入变量
二、JMeter 运行模式与进程
核心结论
JMeter 支持 GUI 模式和命令行(CLI)模式,正式压测必须使用命令行模式,GUI 模式仅用于脚本调试。GUI 模式下 JMeter 自身消耗大量资源,会干扰测试结果。
1. 运行模式对比
| 模式 | 适用场景 | 进程数 | 资源消耗 |
|---|---|---|---|
| GUI 模式 | 脚本录制、调试、结果查看 | 1 个主进程 | 高 |
| CLI 模式 | 正式压测 | 1 个主进程 | 低 |
| 分布式模式 | 超大并发(单机不够用) | 1 Master + N Slaves | 按节点数线性扩展 |
2. 命令行运行
# 基本运行
jmeter -n -t test.jmx -l result.jtl
# 指定日志文件
jmeter -n -t test.jmx -l result.jtl -j jmeter.log
# 运行后生成 HTML 报告
jmeter -n -t test.jmx -l result.jtl -e -o ./report
| 参数 | 说明 |
|---|---|
-n |
非 GUI 模式 |
-t |
测试计划文件(.jmx) |
-l |
结果文件(.jtl) |
-e |
测试结束后生成报告 |
-o |
报告输出目录 |
3. JVM 参数调优
编辑 bin/jmeter 或 bin/jmeter.bat,修改 JVM 参数:
JVM_ARGS="-Xms1g -Xmx4g -XX:MaxMetaspaceSize=512m -XX:+UseG1GC"
| 参数 | 说明 |
|---|---|
-Xms1g |
初始堆内存 1GB |
-Xmx4g |
最大堆内存 4GB |
-XX:MaxMetaspaceSize=512m |
元空间上限 |
-XX:+UseG1GC |
使用 G1 垃圾收集器 |
4. 查看 JMeter 进程
# Linux/macOS
ps aux | grep jmeter
# 查看 Java 进程
jps -l | grep jmeter
# 查看线程级 CPU 占用
top -H -p <JMeter_PID>
三、线程组参数详解
核心结论
线程组的三个核心参数(线程数、Ramp-Up、循环次数)共同决定了测试的并发模式和持续时长。错误的 Ramp-Up 配置会导致压测一开始就打满并发,无法观察系统在不同负载下的表现曲线。
1. 核心参数说明
配置路径: 右键测试计划 → Add → Threads → Thread Group
| 参数 | 说明 |
|---|---|
| Number of Threads(线程数) | 并发用户数,每个线程模拟一个用户 |
| Ramp-Up Period(启动时间/s) | 所有线程在多少秒内全部启动完毕 |
| Loop Count(循环次数) | 每个线程执行测试计划的次数,勾选 Infinite 表示持续运行 |
| Duration(持续时长) | 勾选 Scheduler 后可设置测试持续秒数,比循环次数更精确 |
| Startup delay | 延迟多少秒后开始启动线程 |
2. Ramp-Up 设置策略
Ramp-Up 控制线程启动速率,公式为:
每秒启动线程数 = 线程数 / Ramp-Up 时间(秒)
| 场景 | 推荐设置 |
|---|---|
| 阶梯加压,观察拐点 | Ramp-Up = 线程数(每秒启动 1 个线程) |
| 模拟瞬间涌入 | Ramp-Up = 0 或极小值(所有线程同时启动) |
| 稳定负载测试 | Ramp-Up = 线程数 × 3(缓慢启动,系统有预热时间) |
例如:100 个线程,Ramp-Up 设为 100 秒,则每秒启动 1 个线程,第 100 秒才达到 100 并发。
3. 常用线程组类型
| 线程组类型 | 特点 | 适用场景 |
|---|---|---|
| Thread Group(默认) | 固定线程数,线性启动 | 稳定负载测试 |
| Stepping Thread Group(插件) | 分阶段逐步增加线程数 | 寻找系统性能拐点 |
| Ultimate Thread Group(插件) | 可精细控制每个阶段的线程数和持续时间 | 复杂负载模型 |
| Concurrency Thread Group(插件) | 维持目标并发数恒定 | 恒定并发压测 |
插件安装:JMeter 插件管理器 → 搜索 “Custom Thread Groups” → 安装。
4. 典型配置示例
场景:测试系统在 200 并发下的稳定性,持续 10 分钟
Number of Threads: 200
Ramp-Up Period: 60 ← 60秒内逐步达到200并发
勾选 Scheduler
Duration: 600 ← 持续600秒(10分钟)
Startup delay: 0
场景:寻找系统最大承载量(阶梯加压)
使用 Stepping Thread Group 插件:
Start Threads Count: 10 ← 初始10个线程
Add Threads Every: 30 sec ← 每30秒
Thread Count to Add: 10 ← 增加10个线程
Max Threads Count: 200 ← 最多加到200
四、HTTP 配置元件
核心结论
HTTP Cookie Manager 和 HTTP Header Manager 是 HTTP 测试中最常用的两个配置元件。前者自动管理会话 Cookie,后者统一设置请求头(如 Content-Type、Authorization)。两者放在线程组级别时对所有请求生效,放在单个请求下时仅对该请求生效。
1. HTTP Cookie Manager
作用: 自动收集响应中的 Set-Cookie 并在后续请求中携带,模拟浏览器的 Cookie 行为(保持登录态)。
配置路径: 右键线程组 → Add → Config Element → HTTP Cookie Manager
| 配置项 | 说明 |
|---|---|
| Clear cookies each iteration | 每次循环开始时清除 Cookie(模拟新用户登录) |
| Cookie Policy | 推荐选 standard,兼容大多数场景 |
使用建议:
- 每个线程组添加一个 Cookie Manager
- 需要模拟多用户独立 Cookie 时,勾选 “Clear cookies each iteration” 并配合 CSV Data Set Config 参数化账号
2. HTTP Header Manager
作用: 为请求统一添加 HTTP 请求头,如 Content-Type、Authorization、自定义业务头。
配置路径: 右键线程组(或单个请求)→ Add → Config Element → HTTP Header Manager
常用请求头配置示例:
| Header 名称 | 值 | 说明 |
|---|---|---|
| Content-Type | application/json |
JSON 接口必须设置 |
| Authorization | Bearer ${__P(globalToken,)} |
携带全局 Token |
| Accept | application/json |
期望返回 JSON |
| User-Agent | Mozilla/5.0 ... |
模拟浏览器 UA |
注意: 同一请求上如果有多个 Header Manager(线程组级 + 请求级),两者的 Header 会合并;若存在同名 Header,请求级的会覆盖线程组级的。
3. HTTP Request Defaults
作用: 统一设置所有 HTTP 请求的默认值(服务器地址、端口、协议),避免每个请求都重复填写。
配置路径: 右键线程组 → Add → Config Element → HTTP Request Defaults
| 配置项 | 说明 |
|---|---|
| Server Name or IP | 被测服务器地址,如 api.example.com |
| Port Number | 端口,如 8080 |
| Protocol | http 或 https |
| Content encoding | 编码格式,通常填 UTF-8 |
配置后,各 HTTP 请求只需填写路径(如 /api/users),服务器地址从默认值继承,修改环境时只需改一处。
五、定时器(Timer)
核心结论
定时器用于控制 JMeter 发送请求前的等待时间,模拟真实用户的思考时间(Think Time)。不加定时器时 JMeter 会尽可能快地连续发请求,与真实用户行为差异较大。Synchronizing Timer(同步定时器)是实现集合点(瞬时并发)的专用工具。
1. 常用定时器类型
配置路径: 右键采样器(或线程组)→ Add → Timer → 选择定时器类型
| 定时器类型 | 等待规则 | 适用场景 |
|---|---|---|
| Constant Timer | 固定等待 N 毫秒 | 模拟固定思考时间 |
| Uniform Random Timer | 在 [最小值, 最大值] 范围内均匀随机 | 模拟不规则用户行为 |
| Gaussian Random Timer | 以均值为中心、高斯分布随机 | 更贴近真实用户分布 |
| Synchronizing Timer | 等待 N 个线程全部到达后同时释放 | 瞬时并发(集合点)测试 |
| Poisson Random Timer | 泊松分布随机等待 | 模拟事件到达的随机性 |
2. Constant Timer
最简单的定时器,在每次采样器执行前等待固定时间。
配置:
Thread Delay (in milliseconds): 1000 ← 每次请求前等待1秒
作用域: 放在采样器下只对该采样器生效;放在线程组下对所有采样器生效。
3. Gaussian Random Timer
在固定偏移量基础上叠加高斯分布随机延迟,更贴近真实用户行为。
配置:
Deviation (in milliseconds): 500 ← 标准差500ms
Constant Delay Offset (in milliseconds): 1000 ← 固定基准1000ms
实际等待时间 = 基准值 + 高斯随机值,约 99.7% 的等待时间落在 [基准 - 3×标准差, 基准 + 3×标准差] 范围内。
4. Synchronizing Timer(集合点)
Synchronizing Timer 会让线程等待,直到指定数量的线程全部到达该点后,再同时释放,实现真正的瞬时并发。
配置:
Number of Simulated Users to Group by: 50 ← 等待50个线程全部到达
Timeout in milliseconds: 5000 ← 超时时间,超时则强制释放
典型用途: 测试抢购、秒杀等需要大量用户同一时刻发起请求的场景。
使用建议:
- 集合点线程数 ≤ 实际线程组线程数,否则永远无法凑齐而超时
- 建议同时设置合理的 Timeout,防止测试卡死
Test Plan
└─ Thread Group(线程数: 100)
├─ HTTP Request 1(登录)
└─ Synchronizing Timer(等待50线程)← 50个线程同时到达后同时发下一请求
└─ HTTP Request 2(抢购)
六、参数化与数据驱动
核心结论
参数化是性能测试的基础能力,JMeter 提供 CSV Data Set Config(文件驱动)和 JSON/正则表达式提取器(响应驱动)两种主要方式。前者用于准备输入数据,后者用于提取接口返回值供后续请求使用。
1. CSV Data Set Config
CSV Data Set Config 是最常用的参数化方式,适合并发场景下每个线程使用不同数据。
配置路径: 右键线程组 → Add → Config Element → CSV Data Set Config
| 配置项 | 说明 |
|---|---|
| Filename | CSV 文件路径(建议使用相对路径) |
| Variable Names | 变量名,逗号分隔,对应 CSV 列 |
| Delimiter | 分隔符,默认逗号 |
| Sharing Mode | All threads(所有线程共享)/ Current thread group |
| Recycle on EOF | 文件读完后是否循环 |
| Stop thread on EOF | 文件读完后是否停止线程 |
CSV 文件示例(testdata.csv):
username,password
user001,pass001
user002,pass002
user003,pass003
在 HTTP 请求中引用变量:
用户名字段:${username}
密码字段:${password}
2. JSON 提取器
JSON 提取器用于从 JSON 格式的响应中提取数据,是接口关联的核心工具。
配置路径: 右键 HTTP 请求 → Add → Post Processors → JSON Extractor
| 配置项 | 说明 |
|---|---|
| Names of created variables | 提取后存储的变量名 |
| JSON Path expressions | JSONPath 表达式 |
| Match No. | 0=随机,1=第一个,-1=全部 |
| Default Value | 未匹配时的默认值 |
假设响应体为:
{
"code": 200,
"data": {
"token": "eyJhbGciOiJIUzI1NiJ9...",
"userId": 1001
}
}
提取 token 的 JSONPath 表达式:
$.data.token
提取后在后续请求中引用:
Authorization: Bearer ${token}
3. 正则表达式提取器
正则表达式提取器适用于非 JSON 格式的响应,或需要提取响应头中的数据。
配置路径: 右键 HTTP 请求 → Add → Post Processors → Regular Expression Extractor
| 配置项 | 说明 |
|---|---|
| Field to check | 检查范围:Body / Response Headers / URL |
| Reference Name | 变量名 |
| Regular Expression | 正则表达式,用 () 包裹要提取的部分 |
| Template | $1$ 表示第一个捕获组 |
| Match No. | 1=第一个匹配 |
从响应头提取 Location 重定向地址:
Field to check: Response Headers
Regular Expression: Location: (.*?)\r\n
Template: $1$
七、内置函数(Functions)
核心结论
JMeter 内置函数以 ${__functionName(params)} 语法调用,无需编写脚本即可实现动态参数生成。常用函数覆盖随机数、时间戳、UUID、计数器、属性读写等高频场景,是参数化的轻量替代方案。
1. 函数助手
打开方式: 菜单栏 → Options → Function Helper Dialog
函数助手提供可视化配置界面:选择函数名 → 填写参数 → 点击 Generate → 复制生成的函数表达式。
2. 常用函数一览
| 函数 | 说明 | 示例 |
|---|---|---|
${__Random(1,100,)} |
生成指定范围内的随机整数 | ${__Random(1,1000,)} 生成 1~1000 的随机数 |
${__time(,)} |
返回当前时间戳(毫秒) | ${__time(,)} |
${__time(yyyy-MM-dd,)} |
格式化当前日期 | ${__time(yyyy-MM-dd HH:mm:ss,)} |
${__UUID()} |
生成 UUID | 用于唯一标识符字段 |
${__counter(TRUE,)} |
线程内自增计数器(每次调用+1) | ${__counter(FALSE,)} 全局计数器 |
${__threadNum} |
当前线程编号(从 1 开始) | 区分不同线程的请求 |
${__P(key,default)} |
读取 JMeter Properties | ${__P(globalToken,)} |
${__setProperty(key,value,)} |
写入 JMeter Properties | ${__setProperty(env,prod,)} |
${__eval(expression)} |
对表达式求值(支持嵌套变量) | ${__eval(${varName})} |
${__groovy(expression,)} |
执行 Groovy 表达式 | ${__groovy(new Date().format('yyyyMMdd'),)} |
3. 实战示例
生成唯一用户名(时间戳 + 随机数):
user_${__time(,)}_${__Random(1000,9999,)}
生成带日期的订单号:
ORDER_${__time(yyyyMMdd,)}_${__counter(FALSE,)}
读取命令行传入的参数(-Jenv=prod):
${__P(env,dev)} ← 默认值为 dev,命令行传 -Jenv=prod 时使用 prod
命令行运行时传入参数:
jmeter -n -t test.jmx -l result.jtl -Jenv=prod -Jbaseurl=https://api.example.com
动态生成 JSON 请求体:
在 HTTP 请求的 Body Data 中直接使用函数:
{
"requestId": "${__UUID()}",
"timestamp": "${__time(,)}",
"userId": "${__Random(1000,9999,)}",
"username": "user_${__threadNum}"
}
八、逻辑控制器
核心结论
逻辑控制器控制采样器的执行逻辑,包括条件分支、循环、遍历变量、随机选择等。合理使用逻辑控制器可以模拟真实用户行为(如按概率随机访问不同页面),减少重复请求配置,使测试场景更贴近生产流量。
1. 常用逻辑控制器
配置路径: 右键线程组 → Add → Logic Controller → 选择类型
| 控制器 | 功能 | 典型用途 |
|---|---|---|
| If Controller | 条件为真时执行子节点 | 登录成功才执行后续操作 |
| Loop Controller | 循环执行子节点 N 次 | 重复某个操作指定次数 |
| ForEach Controller | 遍历一组变量(_1, _2…) | 遍历 JSON 提取器提取的多个值 |
| Once Only Controller | 每个线程只执行一次 | 登录操作只执行一次 |
| Interleave Controller | 每次循环轮流执行一个子节点 | 模拟用户依次浏览不同页面 |
| Random Controller | 每次随机执行一个子节点 | 模拟随机行为 |
| Random Order Controller | 随机顺序执行所有子节点 | 随机化操作顺序 |
2. If Controller
基于条件表达式决定是否执行子节点,条件为 true 时执行。
配置路径: 右键线程组 → Add → Logic Controller → If Controller
| 配置项 | 说明 |
|---|---|
| Condition | 条件表达式,结果为 true 或 false |
| Interpret Condition as Variable Expression | 勾选后条件可以直接使用 ${varName} 格式 |
| Evaluate for all children | 勾选后对每个子节点都重新评估条件 |
条件表达式示例(使用 __jexl3 函数):
${__jexl3("${responseCode}" == "200",)}
${__jexl3(${errorCount} > 0,)}
${__jexl3("${userRole}" == "admin",)}
3. ForEach Controller
遍历 JSON 提取器或 CSV 提取的多个值,适合处理列表类响应数据。
配置:
Input variable prefix: userId ← 变量前缀(对应 userId_1, userId_2...)
Start index for loop: 1
End index for loop: ${userId_matchNr} ← matchNr 是提取器自动生成的总数变量
Output variable name: currentUserId ← 每次迭代的当前值
使用场景:JSON 提取器设置 Match No. = -1 提取所有匹配值,ForEach Controller 遍历每个值依次发请求:
Test Plan
└─ Thread Group
├─ HTTP Request(获取用户列表)
│ └─ JSON Extractor(变量名: userId, Match No: -1)← 提取所有 userId
└─ ForEach Controller(prefix: userId, output: currentUserId)
└─ HTTP Request(获取用户详情 /users/${currentUserId})
4. Once Only Controller
Once Only Controller 内的采样器在整个测试中每个线程只执行一次(无论循环多少次),适合放登录操作。
Test Plan
└─ Thread Group(Loop Count: 10)
├─ Once Only Controller
│ └─ HTTP Request(登录,只执行1次)
│ └─ JSON Extractor(提取 Token)
└─ HTTP Request(业务请求,执行10次)← 使用登录获取的 Token
5. Random Controller
Random Controller 每次循环随机选择一个子节点执行,用于模拟用户随机浏览行为。
Test Plan
└─ Thread Group
└─ Random Controller
├─ HTTP Request(首页)
├─ HTTP Request(商品列表)
├─ HTTP Request(搜索)
└─ HTTP Request(商品详情)
每次循环随机执行其中一个请求,模拟真实用户的随机访问模式。
九、断言(Assertion)
核心结论
断言用于验证服务器响应是否符合预期,是区分"请求成功"和"业务正确"的关键手段。HTTP 状态码 200 只代表请求通了,不代表业务逻辑正确,必须通过断言校验响应内容。
1. 常用断言类型
| 断言类型 | 适用场景 |
|---|---|
| 响应断言(Response Assertion) | 校验响应体/响应码/响应头包含或匹配指定文本 |
| JSON 断言(JSON Assertion) | 校验 JSON 响应中某个字段的值 |
| 持续时间断言(Duration Assertion) | 校验响应时间不超过指定毫秒数 |
| 大小断言(Size Assertion) | 校验响应体字节数在指定范围内 |
| JSR223 断言 | 用 Groovy 脚本编写自定义校验逻辑 |
2. 响应断言配置
配置路径: 右键 HTTP 请求 → Add → Assertions → Response Assertion
| 配置项 | 说明 |
|---|---|
| Field to Test | 校验范围:Response Body / Response Code / Response Headers |
| Pattern Matching Rules | Contains(包含)/ Equals(完全匹配)/ Matches(正则)/ Not(取反) |
| Patterns to Test | 期望出现的字符串或正则 |
校验响应码为 200:
Field to Test: Response Code
Pattern Matching Rules: Equals
Patterns to Test: 200
校验响应体包含 "success":true:
Field to Test: Response Body
Pattern Matching Rules: Contains
Patterns to Test: "success":true
3. JSON 断言配置
配置路径: 右键 HTTP 请求 → Add → Assertions → JSON Assertion
JSON 断言直接基于 JSONPath 校验字段值,比响应断言更精准。
| 配置项 | 说明 |
|---|---|
| Assert JSON Path exists | JSONPath 表达式,如 $.data.token |
| Additionally assert value | 勾选后可校验具体值 |
| Expected value | 期望的字段值 |
| Invert assertion | 取反,校验该字段不存在或值不匹配 |
校验响应中 code 字段等于 200:
Assert JSON Path exists: $.code
Additionally assert value: 勾选
Expected value: 200
4. JSR223 断言(自定义逻辑)
配置路径: 右键 HTTP 请求 → Add → Assertions → JSR223 Assertion
当需要复杂校验逻辑时(如比较两个字段、校验数组长度),使用 JSR223 断言:
import groovy.json.JsonSlurper
def response = new JsonSlurper().parseText(prev.getResponseDataAsString())
// 校验 code 为 200 且 data 不为空
if (response.code != 200 || response.data == null) {
AssertionResult.setFailure(true)
AssertionResult.setFailureMessage("断言失败:code=${response.code}, data=${response.data}")
}
5. 断言失败的处理
断言失败时,该请求在聚合报告中计入 Error%,在查看结果树中标红显示失败原因。
注意事项:
- 断言应放在需要校验的采样器下,作为子节点
- 放在线程组下则对该线程组所有请求生效
- 正式压测时建议只保留关键断言,过多断言会影响性能
十、脚本录制与 HTTPS 配置
核心结论
JMeter 内置 HTTP(S) Test Script Recorder 可以录制浏览器操作生成测试脚本。录制 HTTPS 请求需要将 JMeter 生成的根证书安装到浏览器,否则浏览器会拒绝 JMeter 的代理连接。
1. 录制器配置步骤
第一步:启动录制器
右键测试计划 → Add → Non-Test Elements → HTTP(S) Test Script Recorder
| 配置项 | 说明 |
|---|---|
| Port | 代理端口,默认 8888 |
| Target Controller | 录制结果存放的位置(通常选线程组) |
| URL Patterns to Include | 只录制匹配的 URL,如 .*api.* |
| URL Patterns to Exclude | 排除静态资源,如 .*\.(css|js|png|jpg) |
第二步:配置浏览器代理
浏览器网络设置 → 手动代理 → HTTP 代理:localhost,端口:8888
第三步:安装 JMeter 根证书(HTTPS 必须)
启动录制器后,浏览器访问 http://localhost:8888,下载 JMeter 根证书(ApacheJMeterTemporaryRootCA.crt)。
Chrome 安装证书路径:
设置 → 隐私和安全 → 安全 → 管理证书 → 受信任的根证书颁发机构 → 导入
Firefox 安装证书路径:
设置 → 隐私与安全 → 证书 → 查看证书 → 证书颁发机构 → 导入
注意: JMeter 根证书有效期为 7 天,过期后需重新生成并重新安装。
2. 跨域重定向抓包
当应用存在跨域重定向(如 OAuth 登录跳转到第三方域名)时,默认配置可能丢失中间请求。
方案:禁用自动重定向,手动处理
在 HTTP 请求中取消勾选 Follow Redirects 和 Redirect Automatically,JMeter 将返回 3xx 响应,可在结果树中看到所有中间跳转。
从 3xx 响应头提取 Location 地址:
Field to check: Response Headers
Regular Expression: Location: (.*?)\r\n
Template: $1$
变量名:redirectURL
后续请求使用 ${redirectURL} 作为路径,并在 HTTP 头管理器中补充跨域头:
Origin: ${目标域名}
Referer: ${来源页面URL}
十一、JSR223 脚本使用
核心结论
JMeter 支持 BeanShell 和 JSR223 两种脚本处理器,官方推荐使用 JSR223 + Groovy。Groovy 编译为字节码执行,性能远优于 BeanShell 的解释执行,且支持现代 Java/Groovy 语法。
1. BeanShell vs JSR223
| 特性 | BeanShell | JSR223 + Groovy |
|---|---|---|
| 执行方式 | 解释执行 | 编译为字节码 |
| 性能 | 较差,高并发下影响明显 | 优秀 |
| 语法支持 | Java 1.5 基础语法,不支持 ?.、lambda |
支持现代 Groovy/Java 语法 |
| 推荐程度 | 不推荐(遗留兼容) | 推荐 |
2. 常用内置变量
在 JSR223 脚本中可直接使用以下内置对象:
| 变量 | 类型 | 说明 |
|---|---|---|
| vars | JMeterVariables | 读写当前线程变量 |
| props | Properties | 读写全局属性 |
| prev | SampleResult | 上一个请求的结果 |
| sampler | HTTPSamplerProxy | 当前采样器(仅 PostProcessor 中自动可用) |
| log | Logger | 写日志 |
| ctx | JMeterContext | 当前线程上下文 |
3. 常用脚本示例
将上一个请求的完整响应体存入变量:
vars.put("full_response", prev.getResponseDataAsString())
从响应头中遍历查找特定 Header(getHeader(String) 方法不存在,必须遍历):
try {
def referer = sampler.getHeaderManager()?.getHeaders()?.find {
it.getName().equalsIgnoreCase("Referer")
}?.getValue()
if (referer) {
def query = new URI(referer).query
query?.split('&')?.each { pair ->
def (key, value) = pair.split('=', 2).collect { URLDecoder.decode(it, "UTF-8") }
if (key == "code") {
vars.put("extracted_code", value)
log.info("Extracted code: " + value)
}
}
}
} catch (Exception e) {
log.error("Error extracting code: ", e)
}
在 PreProcessor 中手动获取当前 Sampler(sampler 变量在 PreProcessor 中不自动注入):
import org.apache.jmeter.threads.JMeterContextService
def sampler = JMeterContextService.getContext().getCurrentSampler()
if (sampler != null) {
// 操作 sampler
}
4. 将上一个请求响应作为下一个请求的 Body
方法一:JSR223 PostProcessor 存入变量
vars.put("full_response", prev.getResponseDataAsString())
在下一个 POST 请求的 Body Data 中引用:
${full_response}
方法二:正则表达式提取器提取整个响应
Field to check: Body
Regular Expression: (?s)(.*)
Template: $1$
变量名:response_data
方法三:直接引用前一个响应(最简单)
${__prev(ResponseData)}
十二、跨线程组变量共享
核心结论
JMeter 中 vars 变量只在当前线程内有效,跨线程组共享数据必须使用 props(JMeter Properties)。典型场景:第一个线程组登录获取 Token,后续线程组的所有请求都需要携带该 Token。
1. vars vs props 区别
| 特性 | vars(Variables) | props(Properties) |
|---|---|---|
| 作用域 | 当前线程内 | 全局,所有线程组可见 |
| 生命周期 | 线程结束即销毁 | 测试计划运行期间持续存在 |
| 写入方式 | vars.put("key", value) |
props.put("key", value) |
| 读取方式 | ${varName} 或 vars.get("key") |
${__P(key,)} 或 props.get("key") |
| 分布式测试 | 不跨节点 | 需手动同步到各 Slave 节点 |
2. 完整实现步骤
第一步:提取 Token(线程组 1)
在登录请求后添加 JSON 提取器,将 Token 存入局部变量:
变量名:token
JSONPath:$.data.token
第二步:写入全局属性(JSR223 PostProcessor,Groovy)
def token = vars.get('token')
props.put('globalToken', token)
log.info("Token saved to props: " + token)
第三步:其他线程组读取 Token
在 HTTP 请求的 Header Manager 中添加:
Authorization: Bearer ${__P(globalToken,)}
或在 JSR223 脚本中读取:
def token = props.get('globalToken')
3. 使用 __setProperty 函数(无需脚本)
也可以直接在 BeanShell/JSR223 后置处理器中用函数写入:
${__setProperty(globalToken,${token},)}
读取时同样使用:
${__P(globalToken,)}
十三、事务控制器
核心结论
事务控制器(Transaction Controller)将多个请求合并为一个逻辑事务,在聚合报告中生成该事务的整体响应时间和 TPS 指标。适合评估一个完整用户操作(如"登录→查询→下单")的端到端性能。
1. 使用场景
| 场景 | 是否需要事务控制器 |
|---|---|
| 评估页面整体加载时间 | 需要,将页面所有请求包裹为一个事务 |
| 独立分析每个接口性能 | 不需要,保持请求独立 |
| 逻辑分组,便于报告阅读 | 需要 |
| 并行请求的精确耗时统计 | 不适合(事务时间是子请求累计时间,非并行最大值) |
2. 配置说明
配置路径: 右键线程组 → Add → Logic Controller → Transaction Controller
| 配置项 | 说明 |
|---|---|
| Generate parent sample | 勾选后在报告中生成父事务样本,汇总子请求总耗时 |
| Include duration of timer | 是否将定时器时间计入事务耗时 |
注意: 事务控制器的总耗时是所有子请求的累计时间。若请求实际是并行发送的,事务时间会大于实际用户感知时间,需结合 Response Times Over Time 监听器分析。
3. 并行请求实现
JMeter 默认按顺序执行请求,模拟并行需要额外配置:
Test Plan
└─ Thread Group
└─ Transaction Controller(Generate parent sample: true)
└─ Parallel Controller(需安装插件)
├─ HTTP Request 1
├─ HTTP Request 2
└─ HTTP Request 3
Parallel Controller 插件安装: JMeter 插件管理器 → 搜索 “Parallel Controller” → 安装。
十四、JDBC 数据库测试
核心结论
JMeter 通过 JDBC Request 采样器直接操作数据库,适合验证接口写入结果、准备测试数据、或对数据库本身进行性能测试。使用前必须先配置 JDBC Connection Configuration 并将对应数据库驱动放入 JMeter 的 lib 目录。
1. 配置步骤
第一步:添加驱动
将数据库 JDBC 驱动 jar 包(如 mysql-connector-java-8.x.jar)放入 JMeter 的 lib/ 目录,重启 JMeter。
第二步:配置连接池
右键线程组 → Add → Config Element → JDBC Connection Configuration
| 配置项 | 示例值 |
|---|---|
| Variable Name | dbPool(后续 JDBC Request 引用此名) |
| Database URL | jdbc:mysql://localhost:3306/testdb |
| JDBC Driver class | com.mysql.cj.jdbc.Driver |
| Username | root |
| Password | your_password |
第三步:添加 JDBC Request
右键线程组 → Add → Sampler → JDBC Request
| 配置项 | 说明 |
|---|---|
| Variable Name | 填写连接池名称,如 dbPool |
| Query Type | Select Statement / Update Statement / Callable Statement |
| SQL Query | 要执行的 SQL 语句 |
| Variable names | 查询结果存储的变量名(逗号分隔) |
查询示例:
SELECT user_id, username FROM users WHERE status = 1 LIMIT 10
将结果存入变量(Variable names 填 userId,userName),后续用 ${userId_1}、${userName_1} 引用第一行数据。
十五、性能指标与吞吐量
核心结论
吞吐量(Throughput)是性能测试最核心的指标,反映系统单位时间内处理请求的能力。JMeter 聚合报告中的 Throughput 单位为 requests/second,计算公式为:总请求数 ÷ 总测试时间。
1. 核心性能指标
| 指标 | 说明 | 参考标准 |
|---|---|---|
| Throughput(吞吐量) | 每秒处理的请求数(TPS/RPS) | 越高越好 |
| Average(平均响应时间) | 所有请求的平均耗时(ms) | 越低越好 |
| 90% Line | 90% 的请求在此时间内完成 | 重要参考值 |
| 99% Line | 99% 的请求在此时间内完成 | 长尾延迟指标 |
| Error%(错误率) | 失败请求占总请求的比例 | 正常应 < 1% |
| Min / Max | 最快/最慢响应时间 | 关注 Max 异常值 |
2. 吞吐量计算
吞吐量计算公式:
Throughput = Total Samples / Total Time (seconds)
例如:10 分钟内发送了 60000 个请求:
Throughput = 60000 / 600 = 100 requests/second
影响吞吐量的因素:
- 服务器处理能力(CPU、内存、线程池)
- 数据库查询效率
- 网络带宽
- JMeter 压测机本身性能
3. 聚合报告字段说明
聚合报告(Aggregate Report)是最常用的结果分析监听器。
| 字段 | 说明 |
|---|---|
| Label | 采样器名称或事务名称 |
| # Samples | 总请求数 |
| Average | 平均响应时间(ms) |
| Median | 中位数响应时间(ms) |
| 90% Line | 90 百分位响应时间 |
| 95% Line | 95 百分位响应时间 |
| 99% Line | 99 百分位响应时间 |
| Min | 最小响应时间 |
| Max | 最大响应时间 |
| Error% | 错误率 |
| Throughput | 吞吐量(requests/sec) |
| Received KB/sec | 每秒接收数据量 |
十六、服务器资源监控
核心结论
JMeter 本身只能监控接口响应指标,要同时监控服务器的 CPU、内存、磁盘 IO 等资源,需要安装 PerfMon 插件并在被测服务器上运行 ServerAgent。这是定位性能瓶颈(如内存泄漏、CPU 飙高)的必要手段。
1. PerfMon 插件配置
安装步骤:
- 下载 JMeter Plugins Manager,放入
lib/ext/目录 - 重启 JMeter → Options → Plugins Manager
- 搜索 “PerfMon” → 安装 → 重启
被测服务器配置:
- 下载 ServerAgent(
ServerAgent-x.x.zip) - 上传到被测服务器,解压后执行:
# Linux
./startAgent.sh
# Windows
startAgent.bat
默认监听端口 4444。
JMeter 端配置:
右键线程组 → Add → Listener → jp@gc - PerfMon Metrics Collector
| 配置项 | 说明 |
|---|---|
| Host/IP | 被测服务器 IP |
| Port | ServerAgent 端口,默认 4444 |
| Metric to collect | CPU / Memory / Disk I/O / Network I/O |
2. 内存泄漏测试方法
内存泄漏的特征:随着测试时间增加,内存持续上涨,不会回落。
测试步骤:
- 配置 PerfMon 监控服务器内存(Memory → Used)
- 设置较长的测试时间(如 30 分钟持续压测)
- 观察内存曲线:
- 正常:内存在一定范围内波动,GC 后回落
- 泄漏:内存持续单调递增,不回落
结合 JVM 监控:
# 每秒输出 GC 情况
jstat -gcutil <PID> 1000
# 导出堆转储分析
jmap -dump:format=b,file=heap.hprof <PID>
3. 压测中 CPU/内存高的常见原因
压测机(JMeter 本身)CPU 高:
| 原因 | 解决方案 |
|---|---|
| 使用 GUI 模式运行 | 改用命令行模式:jmeter -n -t test.jmx -l result.jtl |
| 启用了大量监听器 | 正式压测时禁用"查看结果树"等图形监听器 |
| 使用 BeanShell 脚本 | 改用 JSR223 + Groovy |
| JVM 堆内存不足,频繁 GC | 调整启动参数:-Xms1g -Xmx4g |
被测服务器 CPU 高但并发量不大:
| 原因 | 排查方法 |
|---|---|
| 单请求触发复杂业务逻辑 | 用 Arthas/JProfiler 分析热点代码 |
| 数据库慢查询(全表扫描) | 开启慢查询日志,用 EXPLAIN 分析执行计划 |
| 线程池/连接池耗尽,大量等待 | 监控线程池活跃数和等待队列 |
| 缓存命中率低,请求穿透到 DB | 监控 Redis 命中率 |
| JVM 频繁 Full GC | 分析 GC 日志,检查内存泄漏 |
4. 高并发下错误率上升但 CPU/内存正常的原因
这种情况通常是资源竞争或配置限制,而非计算瓶颈。
| 可能原因 | 说明 |
|---|---|
| 线程池耗尽 | Tomcat maxThreads 不足,请求排队超时 |
| 数据库连接池耗尽 | HikariCP 最大连接数过小 |
| 锁竞争 | synchronized 块或数据库行锁导致线程阻塞 |
| 下游服务限流 | 第三方 API 返回 429 Too Many Requests |
| TCP 连接数限制 | 系统 net.core.somaxconn 参数过低 |
| 网关限流 | Nginx/Spring Cloud Gateway 配置了限流规则 |
排查步骤:
# 查看 Java 线程堆栈,找阻塞点
jstack <PID> > threads.txt
# 查看数据库锁等待(MySQL)
SHOW ENGINE INNODB STATUS;
# 查看系统连接数
ss -s
十七、缓存测试
核心结论
JMeter 可以通过 JSR223 Sampler 集成 Jedis 客户端直接操作 Redis,用于测试缓存雪崩、缓存穿透等场景。测试时需结合 Redis 监控(redis-cli monitor)和数据库监控,观察缓存失效时数据库的压力变化。
1. 缓存雪崩测试
缓存雪崩:大量缓存同时失效,请求全部打到数据库。
测试步骤:
- 预热 Redis,填充测试数据
- 使用 JMeter 批量删除缓存,模拟同时失效
- 立即发送大量并发请求
- 监控数据库 QPS 和响应时间变化
JSR223 Sampler(Groovy)模拟缓存雪崩场景:
import redis.clients.jedis.Jedis
def jedis = new Jedis("localhost", 6379)
def key = "test_key_" + vars.get("__threadNum")
def value = jedis.get(key)
if (value == null) {
// 缓存未命中,模拟数据库查询
sleep(100)
jedis.set(key, "value_from_db", "NX", "EX", 60)
}
jedis.close()
return "Success"
2. 缓存穿透测试
缓存穿透:查询不存在的 key,每次都穿透到数据库。
测试步骤:
- 准备大量不存在于 Redis 中的 key 列表(CSV 文件)
- 使用 CSV Data Set Config 加载这些 key
- 并发请求,观察数据库查询量
JSR223 Sampler(Groovy)模拟缓存穿透:
import redis.clients.jedis.Jedis
def jedis = new Jedis("localhost", 6379)
// 生成随机不存在的 key
def randomKey = "non_existent_" + System.currentTimeMillis() + "_" + (Math.random() * 10000).toInteger()
def value = jedis.get(randomKey)
if (value == null) {
// 穿透到数据库,不写入缓存
sleep(100)
}
jedis.close()
return "Requested: " + randomKey
3. 关键监控指标
| 指标 | 监控工具 |
|---|---|
| Redis 命中率 | redis-cli info stats → keyspace_hits/keyspace_misses |
| 数据库 QPS | MySQL SHOW STATUS LIKE 'Questions' |
| 数据库连接数 | SHOW STATUS LIKE 'Threads_connected' |
| 服务器 CPU/内存 | PerfMon + ServerAgent |
十八、HTML 报告解读
核心结论
jmeter -e -o 生成的 HTML 报告比聚合报告信息更丰富,包含随时间变化的趋势图。聚合报告只能看平均值,HTML 报告能看出性能在测试过程中是否稳定、何时开始劣化,是压测结果分析的首选输出形式。
1. 生成 HTML 报告
# 测试结束后直接生成
jmeter -n -t test.jmx -l result.jtl -e -o ./report
# 已有 jtl 文件时单独生成报告
jmeter -g result.jtl -o ./report
注意: -o 指定的目录必须为空或不存在,否则报告生成失败。
2. 核心图表说明
Dashboard(仪表板)
报告首页,汇总全局指标:
| 指标 | 说明 |
|---|---|
| APDEX | 应用性能指数(0~1),越接近 1 越好;基于 Toleration(容忍阈值)和 Frustration(挫败阈值)计算 |
| Requests Summary | 请求成功率饼图 |
| Statistics | 与聚合报告类似,含各接口的完整统计数据 |
Response Times Over Time
横轴为时间,纵轴为平均响应时间。用于判断:
- 响应时间是否在测试过程中持续上升(系统在压力下劣化)
- 是否有明显的响应时间突刺(某时刻出现性能抖动)
Hits per Second
每秒实际到达服务器的请求数(吞吐量趋势)。与线程数对比:
- 线程数在增加但 Hits per Second 不增加 → 系统已达瓶颈
- Hits 曲线出现下降 → 服务器开始拒绝请求或响应变慢导致吞吐下降
Response Time Percentiles Over Time
展示 50th、75th、95th、99th 百分位响应时间的时间趋势。比 Average 更能反映长尾问题:
- 99th 远高于 95th → 存在少量极慢请求,排查是否有锁等待或 GC 停顿
Active Threads Over Time
实际活跃线程数随时间的变化。结合 Ramp-Up 配置验证线程是否按预期启动和运行。
Response Time Distribution
响应时间的频率分布直方图。理想情况下应呈正态分布;若出现双峰(两个集中区间)说明存在两类性能表现差异大的请求,需分开分析。
3. APDEX 阈值配置
APDEX 基于两个阈值计算满意度分数:
| 区间 | 用户体验 | 计分权重 |
|---|---|---|
| ≤ Toleration(容忍值) | 满意 | 1.0 |
| Toleration ~ Frustration(挫败值) | 可接受 | 0.5 |
| > Frustration | 不满意 | 0 |
默认阈值:Toleration = 500ms,Frustration = 1500ms。可在 jmeter.properties 中修改:
jmeter.reportgenerator.apdex_satisfied_threshold=500
jmeter.reportgenerator.apdex_tolerated_threshold=1500
十九、常见错误排查
核心结论
JMeter 脚本调试阶段遇到的错误 90% 集中在连接问题、证书问题、编码问题、路径问题和变量提取失败这五类。遇到错误时优先开启"查看结果树",查看请求的实际发送内容和响应内容,而不是猜测。
1. 常见错误速查表
| 错误信息 | 原因 | 解决方案 |
|---|---|---|
Connection refused |
服务未启动或端口不对 | 确认服务状态和端口;检查防火墙规则 |
Non HTTP response code: java.net.SocketTimeoutException |
连接超时 | 调大 HTTP Request 的 Connect Timeout / Response Timeout |
Non HTTP response code: javax.net.ssl.SSLException |
SSL 握手失败 | 安装 JMeter 根证书;或在 HTTP Request 中勾选"Use KeepAlive"并禁用证书验证 |
Response code: 415 |
Content-Type 未设置 | 在 Header Manager 中添加 Content-Type: application/json |
Response code: 401 |
Token 未传或已失效 | 检查 JSON 提取器是否成功提取到 Token;检查 Header Manager 引用变量名是否正确 |
Response code: 400 |
请求体格式错误 | 在查看结果树中查看实际发送的请求体内容 |
| 响应乱码(中文变问号) | 编码不一致 | HTTP Request 的 Content encoding 填 UTF-8;HTTP Request Defaults 同步设置 |
变量取值为 ${varName}(未替换) |
变量未被提取到 | 检查提取器 JSONPath/正则是否正确;查看结果树中的响应体确认格式 |
2. Connection refused 排查步骤
# 确认服务是否监听目标端口
telnet <host> <port>
curl -v http://<host>:<port>/health
# 确认防火墙是否放行
# Linux
iptables -L | grep <port>
# macOS
sudo lsof -i :<port>
3. SSL 证书问题处理
方案一:安装 JMeter 根证书(推荐录制场景)
见"脚本录制与 HTTPS 配置"章节。
方案二:跳过证书验证(适合内网测试环境)
在 jmeter.properties 中添加:
https.use.cached.ssl.context=false
或在 HTTP Request Sampler 中使用自定义 SSL 上下文(JSR223 PreProcessor):
import org.apache.http.conn.ssl.NoopHostnameVerifier
import org.apache.http.ssl.SSLContextBuilder
def sslContext = new SSLContextBuilder().loadTrustMaterial(null, { chain, authType -> true }).build()
ctx.getCurrentSampler().setProperty("https.socketFactory", sslContext.socketFactory)
4. 变量提取失败排查
变量提取失败时,${varName} 会被替换为默认值(未设置默认值则为 ${varName} 原文)。
排查步骤:
- 在查看结果树中查看响应体,确认响应格式是否符合预期
- 检查 JSONPath 表达式是否正确(可用在线工具如
jsonpath.com验证) - 检查 Match No. 设置:提取多个值用
-1,提取第一个用1 - 在提取器后添加 Debug PostProcessor,查看当前所有变量值:
配置路径: 右键 HTTP 请求 → Add → Post Processors → Debug PostProcessor
Debug PostProcessor 会在查看结果树中输出所有 vars 变量,方便确认提取结果。
5. CSV 文件路径问题
| 问题 | 原因 | 解决方案 |
|---|---|---|
java.io.FileNotFoundException |
路径不对或文件不存在 | 使用相对路径(相对于 .jmx 文件所在目录) |
| 所有线程读取同一行数据 | Sharing Mode 设置为 Current thread | 改为 All threads |
| 数据读完后报错停止 | Stop thread on EOF = True | 改为 False,配合 Recycle on EOF = True |
| 中文乱码 | CSV 文件编码问题 | 将文件保存为 UTF-8 无 BOM 格式 |
CSV 文件路径推荐写法(相对路径):
${__P(user.dir,)}/testdata/users.csv
或直接写相对于脚本的路径:
testdata/users.csv
更多推荐



所有评论(0)