文章目录
- 前言
-
- 第一部分:基础教程
-
- [🛠️ 第一步:安装适配 Java 8 的 JMeter](#🛠️ 第一步:安装适配 Java 8 的 JMeter)
-
- [1. 选择正确的版本](#1. 选择正确的版本)
- [2. 下载与解压](#2. 下载与解压)
- [3. 启动验证](#3. 启动验证)
- [4. 界面汉化(可选)](#4. 界面汉化(可选))
- [🚀 第二步:创建你的第一个测试计划Demo](#🚀 第二步:创建你的第一个测试计划Demo)
-
- [1. 添加线程组 (模拟用户)](#1. 添加线程组 (模拟用户))
- [2. 添加 HTTP 请求 (模拟操作)](#2. 添加 HTTP 请求 (模拟操作))
- [3. 添加监听器 (查看结果)](#3. 添加监听器 (查看结果))
- [📊 第三步:运行与分析](#📊 第三步:运行与分析)
-
- [1. 运行测试](#1. 运行测试)
- [2. 结果分析](#2. 结果分析)
- 第二部分:进阶教程
- 第三部分:进阶分析
-
- 第一步:构建测试计划(接口配置http请求)
-
- [1. 进阶核心点:跨线程组传参(token 共享)](#1. 进阶核心点:跨线程组传参(token 共享))
- [2. 阶梯加压策略(Stepping Thread Group)](#2. 阶梯加压策略(Stepping Thread Group))
- [3. 参数化配置(CSV Data Set Config)](#3. 参数化配置(CSV Data Set Config))
- [4. 关键配置细节审查](#4. 关键配置细节审查)
- [5. 整体流程逻辑梳理](#5. 整体流程逻辑梳理)
- [6. 下一步建议](#6. 下一步建议)
- 第二步:结果分析(监听器详解)
-
- [1. 查看结果树](#1. 查看结果树)
- [2. jp@gc - Active Threads Over Time](#2. jp@gc - Active Threads Over Time)
- [3. jp@gc - Response Times Over Time](#3. jp@gc - Response Times Over Time)
- [4. jp@gc - Transactions per Second](#4. jp@gc - Transactions per Second)
- [5. 聚合报告](#5. 聚合报告)
- 第四部分:脚本优化
-
-
- [1. 替换 BeanShell(性能优化)](#1. 替换 BeanShell(性能优化))
- [2. 完善跨线程组取值](#2. 完善跨线程组取值)
- [3. 添加事务控制器](#3. 添加事务控制器)
-
- 第五部分:执行与验证步骤
- 第六部分:技巧
-
- [1. 挡板机制](#1. 挡板机制)
-
- [①🧪 使用 WireMock(最推荐,轻量级)](#①🧪 使用 WireMock(最推荐,轻量级))
- [②⚙️ JMeter 内部实现 Mock(无需额外工具)](#②⚙️ JMeter 内部实现 Mock(无需额外工具))
-
- [方案一:使用 JSR223 Sampler 模拟 HTTP 服务(最灵活)](#方案一:使用 JSR223 Sampler 模拟 HTTP 服务(最灵活))
- [方案二:使用 JMeter 原生 HTTP Mirror Server(最简单)](#方案二:使用 JMeter 原生 HTTP Mirror Server(最简单))
- 方案对比总结
- [③🛡️ 使用 Nginx 反向代理(运维级方案)](#③🛡️ 使用 Nginx 反向代理(运维级方案))
- [④📊 方案对比与总结](#④📊 方案对比与总结)
前言
JMeter
JMeter是基于Java语言开发的开源轻量级测试工具。
第一部分:基础教程
| JMeter 版本 | 最低 Java 要求 | 你的环境 (Java 8) |
|---|---|---|
| JMeter 6.x (最新版) | Java 17 | ❌ 不支持 (无法启动) |
| JMeter 5.7 - 5.9 | Java 11+ | ❌ 不支持 (无法启动) |
| JMeter 5.6.3 | Java 8+ | ✅ 完美支持 |
由于 Apache JMeter 的最新版本(5.6+)已经不再支持 Java 8,如果需要使用当前的 Java 环境,必须安装旧版本的 JMeter 。以下介绍Java 8 专属的安装与使用教程。
🛠️ 第一步:安装适配 Java 8 的 JMeter
1. 选择正确的版本
- 核心规则:JMeter 5.5 及之前的版本支持 Java 8。
- 推荐版本 :JMeter 5.5(这是支持 Java 8 的最后一个稳定版本,功能足够强大)。
- 避坑指南 :千万不要 下载 JMeter 5.6、5.6.3 或更高版本,否则启动时会报错提示
Java version not supported。
2. 下载与解压
- 下载地址 :访问 Apache JMeter 官方下载归档。
- 获取文件 :找到并下载
apache-jmeter-5.5.zip。 - 解压 :将压缩包解压到一个路径中不包含中文和空格 的目录(例如
D:\JMeter\apache-jmeter-5.5)。
3. 启动验证
- 进入解压目录的
bin文件夹。 - 双击
jmeter.bat。 - 如果成功弹出图形界面,说明 Java 8 环境与 JMeter 5.5 完美匹配。
4. 界面汉化(可选)
默认是英文界面,建议修改配置永久汉化:
- 打开
bin/jmeter.properties文件。 - 搜索
language=en。 - 修改为
language=zh_CN(去掉前面的#号)。 - 保存并重启 JMeter。
🚀 第二步:创建你的第一个测试计划Demo
我们将模拟 10 个用户 在 10 秒内 启动,并发访问百度首页。
1. 添加线程组 (模拟用户)
线程组是测试的发动机。
- 在左侧"测试计划"上右键 ->
添加->线程 (用户)->线程组。 - 配置参数 :
- 线程数 :
10(代表 10 个虚拟用户)。 - Ramp-Up 时间 (秒) :
10(代表在 10 秒内逐步启动这 10 个用户,避免瞬间冲击)。 - 循环次数 :
1(每个用户执行 1 次任务)。
- 线程数 :
2. 添加 HTTP 请求 (模拟操作)
这是用户具体要执行的动作。
- 右键点击"线程组" ->
添加->取样器->HTTP请求。 - 配置参数 :
- 协议 :
https - 服务器名称或IP :
www.baidu.com - 端口号:(留空)
- 方法 :
GET - 路径 :
/
- 协议 :
3. 添加监听器 (查看结果)
没有监听器,测试数据将无法显示。
- 查看结果树 :用于调试,看请求是否成功。
- 右键"线程组" ->
添加->监听器->查看结果树。
- 右键"线程组" ->
- 聚合报告 :用于性能分析,看响应时间和吞吐量。
- 右键"线程组" ->
添加->监听器->聚合报告。
- 右键"线程组" ->
📊 第三步:运行与分析
1. 运行测试
点击工具栏上方的绿色"播放"按钮 (▶️)。
2. 结果分析
测试完成后,点击"聚合报告",重点关注以下指标:
表格
| 指标名称 | 含义 | 你的关注点 |
|---|---|---|
| Average | 平均响应时间 | 数值越小越好(单位:毫秒)。 |
| 90% Line | 90% 请求的响应时间 | 比如是 50ms,说明 90% 的用户都在 50ms 内得到了响应。 |
| Error % | 错误率 | 必须为 0%。如果有红色报错,说明请求失败。 |
| Throughput | 吞吐量 (TPS/QPS) | 服务器每秒处理的请求数,数值越大性能越好。 |
第二部分:进阶教程
目标的测试计划结构非常清晰,是一个典型的登录获取 Token -> 传递 Token -> 携带 Token 请求业务接口的链路测试场景。
🚀 第一步:构建进阶测试计划
构建一个"登录获取 Token -> 跨线程组传递 -> 业务接口调用"的实战场景。
1. 准备数据文件
CSV Data Set Config

-
操作 :在本地创建一个
codes.csv文件,内容如下:文件内容是模拟认证鉴权Oauth2.0流程的授权码列表。
测试目标是已通过用户名密码认证后进入,获取授权码->获取token->接口交互的流程
csvh3UVsSKprUTK WY2FIJBQBlng nMkIuCspPQvC -
JMeter配置:
- 右键点击"测试计划" -> "添加" -> "配置元件" -> "CSV 数据文件设置"。
- 文件名 :选择你的
codes.csv路径。 - 变量名称 :填写
code(与文件列对应)。 - 作用:让每个线程(用户)登录时使用不同的授权码。
2. 配置登录接口
HTTP Request + HTTP Header Manager
HTTP 请求 + HTTP信息头管理器

操作:右键"线程组" -> "添加" -> "取样器" -> "HTTP 请求"。命名为"登录接口"。
-
配置:
-
协议:HTTPS。
-
服务器名称或 IP :
auth-api-perf.xxx.com.cn。 -
路径 :
/auth/oauth2/begin-by-postal。 -
方法:POST。
-
Body Data(在"发送文件随请求"上方):
json{ "code": "${code}" }注意:这里使用了
${}来调用 CSV 中的变量。
-

信息头管理器,请求头列表中不要多余空行,请求头的键值项也必须准确无空格。
3. 提取 Token
JSON Extractor
JSON提取器

登录成功后,服务器会返回 JSON 数据,我们需要从中提取 token。
- 操作:右键点击"登录接口" -> "添加" -> "后置处理器" -> "JSON 提取器"。
- 配置 :
- 名称:提取请求结果token。
- 作用域:主样本(Main sample only)。
- Names of created variables :
token(定义变量名)。 - JSON Path expressions :
$..value(假设返回的 JSON 中 token 字段叫 value,封装在两层的结构体中,请根据实际返回修改)。 - Match No. :
1。
4. 跨线程组传参
BeanShell PostProcessor
BeanShell 后置处理器

这是进阶的核心。JMeter 默认变量是线程隔离的,为了让后续的"业务线程组"能用到这里的 token,必须将其转为全局属性。
-
操作:右键点击"登录接口" -> "添加" -> "后置处理器" -> "BeanShell 后置处理器"。
-
配置:有以下两种方式可以实现
-
使用 JMeter函数,如,内置的
__setProperty函数。在脚本区域输入以下代码:java${__setProperty(global_token,${token},)};不要加
//注释,因为 BeanShell 可能会尝试解析注释后的符号代码解释:将提取到的局部变量 token 转换为全局属性 global_token。第三个参数 true 表示保存到文件,重启 JMeter 后依然有效(可选)
-
使用 BeanShell 原生 API。在脚本区域输入以下代码:
java// 获取局部变量 token String tokenVal = vars.get("token"); // 判空保护 if (tokenVal != null) { // 设置为全局属性 props.put("token", tokenVal); log.info("成功将 token 存入全局属性: " + tokenVal); } else { log.error("未找到 token 变量,请检查前置提取器"); }代码解释:1. 从 JMeter 变量池(vars) 中获取 token 。
2. 将 token 设置到 JMeter 属性池(props) 中,使其全局有效。- 如果需要保存到文件(持久化),使用 props.put 的第三个参数无法直接实现文件保存。 __setProperty 的 true 参数是保存到 jmeter.properties 文件。在 BeanShell 中,通常 props 是内存级的。如果需要持久化,建议直接用 ${__setProperty(...)} 函数。但如果只是为了跨线程组使用,props.put 就够了。
-
5. 配置业务接口
第二个 HTTP Request

- 操作:右键"线程组" -> "添加" -> "取样器" -> "HTTP 请求"。命名为"查看用户信息"。
- 配置 :
- 路径 :
/auth/detail/perms。 - 方法:GET。
- 路径 :
6. 调用全局 Token
HTTP Header Manager

在业务请求中,我们需要把刚才存到全局属性的 token 取出来,放到请求头里。
- 操作:右键点击"查看用户信息"请求 -> "添加" -> "配置元件" -> "HTTP 信息头管理器"。
- 配置 :
- 名称 :
Authorization。 - 值 :
bearer ${__P(global_token)}。 - 解析:
${__P(global_token)}是 JMeter 函数,用于读取全局属性。
- 名称 :
📊 第二步:运行与结果分析
添加监听器
右键点击"测试计划"或具体的"线程组" -> "添加" -> "监听器",添加以下组件:
- 查看结果树:用于调试,看请求是否成功(绿色钩)。
- 聚合报告:查看平均响应时间、吞吐量(TPS)。
- jp@gc - Active Threads Over Time:查看并发用户数随时间的变化(需安装插件)。
- jp@gc - Response Times Over Time:查看响应时间趋势随时间的变化(需安装插件)。
- jp@gc - Transactions per Second:查看每秒事务数/吞吐量,即 QPS随时间的变化(需安装插件)。
运行测试
点击工具栏上的绿色三角右箭头"启动"按钮。
观察日志
点击工具栏右侧的三角警告牌"日志"按钮。
log
2026-04-10 16:35:08,398 INFO o.a.j.e.StandardJMeterEngine: Running the test!
2026-04-10 16:35:08,399 INFO o.a.j.s.SampleEvent: List of sample_variables: []
2026-04-10 16:35:08,399 INFO o.a.j.g.u.JMeterMenuBar: setRunning(true, *local*)
2026-04-10 16:35:08,399 INFO o.a.j.e.StandardJMeterEngine: Starting setUp thread groups
2026-04-10 16:35:08,399 INFO o.a.j.e.StandardJMeterEngine: Starting setUp ThreadGroup: 1 : setUp Thread Group
2026-04-10 16:35:08,399 INFO o.a.j.e.StandardJMeterEngine: Starting 1 threads for group setUp Thread Group.
2026-04-10 16:35:08,399 INFO o.a.j.e.StandardJMeterEngine: Thread will continue on error
2026-04-10 16:35:08,399 INFO o.a.j.t.ThreadGroup: Starting thread group... number=1 threads=1 ramp-up=1 delayedStart=false
2026-04-10 16:35:08,402 INFO o.a.j.t.ThreadGroup: Started thread group number 1
2026-04-10 16:35:08,402 INFO o.a.j.e.StandardJMeterEngine: Waiting for all setup thread groups to exit
2026-04-10 16:35:08,402 INFO o.a.j.t.JMeterThread: Thread started: setUp Thread Group 1-1
2026-04-10 16:35:08,467 INFO o.a.j.p.j.s.J.JSR223 Sampler: >>> Mock HTTP Server started on port: 48001
2026-04-10 16:35:08,467 INFO o.a.j.t.JMeterThread: Thread is done: setUp Thread Group 1-1
2026-04-10 16:35:08,467 INFO o.a.j.t.JMeterThread: Thread finished: setUp Thread Group 1-1
2026-04-10 16:35:08,468 INFO o.a.j.e.StandardJMeterEngine: All Setup Threads have ended
2026-04-10 16:35:08,733 INFO o.a.j.e.StandardJMeterEngine: Starting ThreadGroup: 1 : 第一轮线程组
2026-04-10 16:35:08,733 INFO o.a.j.e.StandardJMeterEngine: Starting 32 threads for group 第一轮线程组.
2026-04-10 16:35:08,734 INFO o.a.j.e.StandardJMeterEngine: Thread will continue on error
2026-04-10 16:35:08,734 INFO k.a.j.t.AbstractSimpleThreadGroup: Starting thread group number 1 threads 32
2026-04-10 16:35:08,744 INFO o.a.j.t.JMeterThread: Thread started: 第一轮线程组 1-1
2026-04-10 16:35:09,007 INFO k.a.j.t.AbstractSimpleThreadGroup: Started thread group number 1
2026-04-10 16:35:09,007 INFO o.a.j.e.StandardJMeterEngine: Waiting for thread group: 第一轮线程组 to finish before starting next group
2026-04-10 16:35:09,488 INFO o.a.j.t.JMeterThread: Thread started: 第一轮线程组 1-2
2026-04-10 16:35:10,234 INFO o.a.j.t.JMeterThread: Thread started: 第一轮线程组 1-3
2026-04-10 16:35:10,985 INFO o.a.j.t.JMeterThread: Thread started: 第一轮线程组 1-4
2026-04-10 16:35:41,735 INFO o.a.j.t.JMeterThread: Thread started: 第一轮线程组 1-5
2026-04-10 16:35:42,484 INFO o.a.j.t.JMeterThread: Thread started: 第一轮线程组 1-6
2026-04-10 16:35:43,235 INFO o.a.j.t.JMeterThread: Thread started: 第一轮线程组 1-7
2026-04-10 16:35:43,985 INFO o.a.j.t.JMeterThread: Thread started: 第一轮线程组 1-8
2026-04-10 16:36:14,735 INFO o.a.j.t.JMeterThread: Thread started: 第一轮线程组 1-9
2026-04-10 16:36:15,489 INFO o.a.j.t.JMeterThread: Thread started: 第一轮线程组 1-10
2026-04-10 16:36:16,242 INFO o.a.j.t.JMeterThread: Thread started: 第一轮线程组 1-11
2026-04-10 16:36:16,984 INFO o.a.j.t.JMeterThread: Thread started: 第一轮线程组 1-12
2026-04-10 16:36:47,734 INFO o.a.j.t.JMeterThread: Thread started: 第一轮线程组 1-13
2026-04-10 16:36:48,484 INFO o.a.j.t.JMeterThread: Thread started: 第一轮线程组 1-14
2026-04-10 16:36:49,234 INFO o.a.j.t.JMeterThread: Thread started: 第一轮线程组 1-15
2026-04-10 16:36:49,984 INFO o.a.j.t.JMeterThread: Thread started: 第一轮线程组 1-16
2026-04-10 16:37:20,734 INFO o.a.j.t.JMeterThread: Thread started: 第一轮线程组 1-17
2026-04-10 16:37:21,485 INFO o.a.j.t.JMeterThread: Thread started: 第一轮线程组 1-18
2026-04-10 16:37:22,235 INFO o.a.j.t.JMeterThread: Thread started: 第一轮线程组 1-19
2026-04-10 16:37:22,984 INFO o.a.j.t.JMeterThread: Thread started: 第一轮线程组 1-20
2026-04-10 16:37:53,744 INFO o.a.j.t.JMeterThread: Thread started: 第一轮线程组 1-21
2026-04-10 16:37:54,485 INFO o.a.j.t.JMeterThread: Thread started: 第一轮线程组 1-22
2026-04-10 16:37:55,235 INFO o.a.j.t.JMeterThread: Thread started: 第一轮线程组 1-23
2026-04-10 16:37:55,986 INFO o.a.j.t.JMeterThread: Thread started: 第一轮线程组 1-24
2026-04-10 16:38:26,735 INFO o.a.j.t.JMeterThread: Thread started: 第一轮线程组 1-25
2026-04-10 16:38:27,484 INFO o.a.j.t.JMeterThread: Thread started: 第一轮线程组 1-26
2026-04-10 16:38:28,235 INFO o.a.j.t.JMeterThread: Thread started: 第一轮线程组 1-27
2026-04-10 16:38:28,985 INFO o.a.j.t.JMeterThread: Thread started: 第一轮线程组 1-28
2026-04-10 16:38:59,734 INFO o.a.j.t.JMeterThread: Thread started: 第一轮线程组 1-29
2026-04-10 16:39:00,485 INFO o.a.j.t.JMeterThread: Thread started: 第一轮线程组 1-30
2026-04-10 16:39:01,235 INFO o.a.j.t.JMeterThread: Thread started: 第一轮线程组 1-31
2026-04-10 16:39:01,984 INFO o.a.j.t.JMeterThread: Thread started: 第一轮线程组 1-32
2026-04-10 16:40:03,822 INFO o.a.j.t.JMeterThread: Stopping because end time detected by thread: 第一轮线程组 1-3
2026-04-10 16:40:03,822 INFO o.a.j.t.JMeterThread: Thread finished: 第一轮线程组 1-3
2026-04-10 16:40:04,823 INFO o.a.j.t.JMeterThread: Stopping because end time detected by thread: 第一轮线程组 1-2
2026-04-10 16:40:04,823 INFO o.a.j.t.JMeterThread: Thread finished: 第一轮线程组 1-2
2026-04-10 16:40:06,024 INFO o.a.j.t.JMeterThread: Stopping because end time detected by thread: 第一轮线程组 1-4
2026-04-10 16:40:06,024 INFO o.a.j.t.JMeterThread: Thread finished: 第一轮线程组 1-4
2026-04-10 16:40:06,410 INFO o.a.j.t.JMeterThread: Stopping because end time detected by thread: 第一轮线程组 1-1
2026-04-10 16:40:06,411 INFO o.a.j.t.JMeterThread: Thread finished: 第一轮线程组 1-1
2026-04-10 16:40:07,930 INFO o.a.j.t.JMeterThread: Stopping because end time detected by thread: 第一轮线程组 1-8
2026-04-10 16:40:07,930 INFO o.a.j.t.JMeterThread: Thread finished: 第一轮线程组 1-8
2026-04-10 16:40:09,165 INFO o.a.j.t.JMeterThread: Stopping because end time detected by thread: 第一轮线程组 1-9
2026-04-10 16:40:09,166 INFO o.a.j.t.JMeterThread: Thread finished: 第一轮线程组 1-9
2026-04-10 16:40:11,099 INFO o.a.j.t.JMeterThread: Stopping because end time detected by thread: 第一轮线程组 1-10
2026-04-10 16:40:11,099 INFO o.a.j.t.JMeterThread: Thread finished: 第一轮线程组 1-10
2026-04-10 16:40:11,941 INFO o.a.j.t.JMeterThread: Stopping because end time detected by thread: 第一轮线程组 1-6
2026-04-10 16:40:11,943 INFO o.a.j.t.JMeterThread: Thread finished: 第一轮线程组 1-6
2026-04-10 16:40:12,034 INFO o.a.j.t.JMeterThread: Stopping because end time detected by thread: 第一轮线程组 1-7
2026-04-10 16:40:12,035 INFO o.a.j.t.JMeterThread: Thread finished: 第一轮线程组 1-7
2026-04-10 16:40:12,091 INFO o.a.j.t.JMeterThread: Stopping because end time detected by thread: 第一轮线程组 1-16
2026-04-10 16:40:12,092 INFO o.a.j.t.JMeterThread: Thread finished: 第一轮线程组 1-16
2026-04-10 16:40:12,493 INFO o.a.j.t.JMeterThread: Stopping because end time detected by thread: 第一轮线程组 1-11
2026-04-10 16:40:12,493 INFO o.a.j.t.JMeterThread: Thread finished: 第一轮线程组 1-11
2026-04-10 16:40:12,901 INFO o.a.j.t.JMeterThread: Stopping because end time detected by thread: 第一轮线程组 1-15
2026-04-10 16:40:12,901 INFO o.a.j.t.JMeterThread: Thread finished: 第一轮线程组 1-15
2026-04-10 16:40:12,954 INFO o.a.j.t.JMeterThread: Stopping because end time detected by thread: 第一轮线程组 1-5
2026-04-10 16:40:12,954 INFO o.a.j.t.JMeterThread: Thread finished: 第一轮线程组 1-5
2026-04-10 16:40:13,472 INFO o.a.j.t.JMeterThread: Stopping because end time detected by thread: 第一轮线程组 1-14
2026-04-10 16:40:13,473 INFO o.a.j.t.JMeterThread: Thread finished: 第一轮线程组 1-14
2026-04-10 16:40:13,785 INFO o.a.j.t.JMeterThread: Stopping because end time detected by thread: 第一轮线程组 1-13
2026-04-10 16:40:13,786 INFO o.a.j.t.JMeterThread: Thread finished: 第一轮线程组 1-13
2026-04-10 16:40:14,149 INFO o.a.j.t.JMeterThread: Stopping because end time detected by thread: 第一轮线程组 1-12
2026-04-10 16:40:14,149 INFO o.a.j.t.JMeterThread: Thread finished: 第一轮线程组 1-12
2026-04-10 16:40:16,109 INFO o.a.j.t.JMeterThread: Stopping because end time detected by thread: 第一轮线程组 1-20
2026-04-10 16:40:16,109 INFO o.a.j.t.JMeterThread: Thread finished: 第一轮线程组 1-20
2026-04-10 16:40:16,737 INFO o.a.j.t.JMeterThread: Stopping because end time detected by thread: 第一轮线程组 1-17
2026-04-10 16:40:16,737 INFO o.a.j.t.JMeterThread: Thread finished: 第一轮线程组 1-17
2026-04-10 16:40:16,762 INFO o.a.j.t.JMeterThread: Stopping because end time detected by thread: 第一轮线程组 1-19
2026-04-10 16:40:16,762 INFO o.a.j.t.JMeterThread: Thread finished: 第一轮线程组 1-19
2026-04-10 16:40:17,284 INFO o.a.j.t.JMeterThread: Stopping because end time detected by thread: 第一轮线程组 1-18
2026-04-10 16:40:17,284 INFO o.a.j.t.JMeterThread: Thread finished: 第一轮线程组 1-18
2026-04-10 16:40:17,736 INFO o.a.j.t.JMeterThread: Stopping because end time detected by thread: 第一轮线程组 1-23
2026-04-10 16:40:17,737 INFO o.a.j.t.JMeterThread: Thread finished: 第一轮线程组 1-23
2026-04-10 16:40:17,874 INFO o.a.j.t.JMeterThread: Stopping because end time detected by thread: 第一轮线程组 1-21
2026-04-10 16:40:17,874 INFO o.a.j.t.JMeterThread: Thread finished: 第一轮线程组 1-21
2026-04-10 16:40:17,969 INFO o.a.j.t.JMeterThread: Stopping because end time detected by thread: 第一轮线程组 1-24
2026-04-10 16:40:17,969 INFO o.a.j.t.JMeterThread: Thread finished: 第一轮线程组 1-24
2026-04-10 16:40:18,590 INFO o.a.j.t.JMeterThread: Stopping because end time detected by thread: 第一轮线程组 1-22
2026-04-10 16:40:18,590 INFO o.a.j.t.JMeterThread: Thread finished: 第一轮线程组 1-22
2026-04-10 16:40:21,262 INFO o.a.j.t.JMeterThread: Stopping because end time detected by thread: 第一轮线程组 1-27
2026-04-10 16:40:21,263 INFO o.a.j.t.JMeterThread: Thread finished: 第一轮线程组 1-27
2026-04-10 16:40:21,421 INFO o.a.j.t.JMeterThread: Stopping because end time detected by thread: 第一轮线程组 1-28
2026-04-10 16:40:21,421 INFO o.a.j.t.JMeterThread: Thread finished: 第一轮线程组 1-28
2026-04-10 16:40:24,467 INFO o.a.j.t.JMeterThread: Stopping because end time detected by thread: 第一轮线程组 1-31
2026-04-10 16:40:24,467 INFO o.a.j.t.JMeterThread: Thread finished: 第一轮线程组 1-31
2026-04-10 16:40:25,859 INFO o.a.j.t.JMeterThread: Stopping because end time detected by thread: 第一轮线程组 1-26
2026-04-10 16:40:25,859 INFO o.a.j.t.JMeterThread: Thread finished: 第一轮线程组 1-26
2026-04-10 16:40:28,895 INFO o.a.j.t.JMeterThread: Stopping because end time detected by thread: 第一轮线程组 1-30
2026-04-10 16:40:28,895 INFO o.a.j.t.JMeterThread: Thread finished: 第一轮线程组 1-30
2026-04-10 16:40:30,998 INFO o.a.j.t.JMeterThread: Stopping because end time detected by thread: 第一轮线程组 1-29
2026-04-10 16:40:30,998 INFO o.a.j.t.JMeterThread: Thread finished: 第一轮线程组 1-29
2026-04-10 16:40:31,070 INFO o.a.j.t.JMeterThread: Stopping because end time detected by thread: 第一轮线程组 1-32
2026-04-10 16:40:31,070 INFO o.a.j.t.JMeterThread: Thread finished: 第一轮线程组 1-32
2026-04-10 16:40:31,492 INFO o.a.j.t.JMeterThread: Stopping because end time detected by thread: 第一轮线程组 1-25
2026-04-10 16:40:31,493 INFO o.a.j.t.JMeterThread: Thread finished: 第一轮线程组 1-25
2026-04-10 16:40:31,494 INFO o.a.j.e.StandardJMeterEngine: All thread groups have been started
2026-04-10 16:40:31,494 INFO o.a.j.e.StandardJMeterEngine: Starting tearDown thread groups
2026-04-10 16:40:31,494 INFO o.a.j.e.StandardJMeterEngine: Starting tearDown ThreadGroup: 1 : tearDown Thread Group
2026-04-10 16:40:31,494 INFO o.a.j.e.StandardJMeterEngine: Starting 1 threads for group tearDown Thread Group.
2026-04-10 16:40:31,494 INFO o.a.j.e.StandardJMeterEngine: Thread will continue on error
2026-04-10 16:40:31,494 INFO o.a.j.t.ThreadGroup: Starting thread group... number=1 threads=1 ramp-up=1 delayedStart=false
2026-04-10 16:40:31,498 INFO o.a.j.t.ThreadGroup: Started thread group number 1
2026-04-10 16:40:31,498 INFO o.a.j.t.JMeterThread: Thread started: tearDown Thread Group 1-1
2026-04-10 16:40:31,538 INFO o.a.j.p.j.s.J.JSR223 Sampler: >>> Mock HTTP Server stopped.
2026-04-10 16:40:31,538 INFO o.a.j.t.JMeterThread: Thread is done: tearDown Thread Group 1-1
2026-04-10 16:40:31,538 INFO o.a.j.t.JMeterThread: Thread finished: tearDown Thread Group 1-1
2026-04-10 16:40:31,538 INFO o.a.j.e.StandardJMeterEngine: Notifying test listeners of end of test
2026-04-10 16:40:31,538 INFO o.a.j.g.u.JMeterMenuBar: setRunning(false, *local*)
结果解读
- 查看结果树:如果"查看用户信息"显示绿色,且响应数据中有用户信息,说明 token 传递成功。如果显示红色或 401 错误,说明 token 提取或传递失败。
- 聚合报告 :
- # Samples:样本数,即请求总数。
- Average:平均响应时间,越短越好。
- Throughput:吞吐量,即每秒处理请求数,越高越好。
- Error %:错误率,应为 0%。

💡 关键知识点总结
- 作用域:配置元件(如 HTTP 信息头管理器)放在哪个层级,就对哪个层级及其子节点生效。
- 关联:使用 JSON 提取器从上一个请求的响应中提取数据。
- 跨线程组传参 :使用
__setProperty将变量转为属性,使用__P读取属性。 - 参数化:使用 CSV 数据文件设置,实现多用户登录。
第三部分:进阶分析
上面讲述了基础操作,针对具体配置进行进阶分析 。这涉及到了参数化、关联(Correlation)、作用域以及跨线程组传参等核心高级技巧。
第一步:构建测试计划(接口配置http请求)
1. 进阶核心点:跨线程组传参(token 共享)
本测试计划中最关键的进阶点。通常 JMeter 的变量是线程私有的(即线程 A 登录拿到的 token,线程 B 无法直接使用)。但在本计划中,需要实现"一轮线程组登录后,后续请求复用 token" 或者 "不同线程组间共享 token"。
测试计划脚本分析:
- JSON Extractor 从登录接口提取了
token,变量名设为token。 - BeanShell PostProcessor 你使用了脚本
${__setProperty(token,${token},true)}。 - HTTP Header Manager 在后续请求中,你使用了
bearer ${__P(token)}来调用这个值。
深度解析:
使用了 __setProperty 函数将局部变量转换为全局属性(JMeter Property)。
- 优点:实现了跨线程组的数据共享。如果"第一轮线程组"负责登录,"第二轮线程组"负责业务,这种方式可以让第二轮拿到第一轮登录的 Token。
- 风险 :
setProperty是全局的。如果你有 100 个并发用户同时登录,最后存入全局属性的token会互相覆盖,导致所有用户都使用最后那个用户的 token。 - 优化建议 :
- 方案 A (单用户调试/串行测试):脚本写法完全没问题,可以正常工作。
- 方案 B (高并发场景) :如果要做高并发,不建议用全局属性传 token。应该将登录请求和业务请求放在同一个线程组 内,利用 JMeter 的默认变量作用域(同一个线程内变量天然共享),去掉 BeanShell 脚本,直接在 Header Manager 中使用
${token}即可。
2. 阶梯加压策略(Stepping Thread Group)
使用了 jp@gc - Stepping Thread Group 插件。
- 配置:初始 4 线程,每 30 秒增加 4 个线程,直到 32 个,保持 60 秒。
- 用途 :这是非常专业的负载测试(Load Testing)配置,用于观察系统在用户量逐步增加时的性能拐点(TPS 何时开始下降,响应时间何时飙升)。
建议:
你的配置中 This group will start 32 threads 和 Finally, stop 4 threads every 3 seconds 配合得很好,模拟了平滑的上线和下线,避免了瞬间断崖式压力。
3. 参数化配置(CSV Data Set Config)
-
配置 :读取
codes.csv,变量名为code,Recycle on EOF设为True。 -
场景 :用于登录接口,传入不同的
code。
注意点:
- Sharing Mode :你选择了
All threads。这意味着所有线程(用户)会依次读取 CSV 文件中的行(线程 1 读第 1 行,线程 2 读第 2 行...)。 - Recycle on EOF = True :如果 CSV 里的
code用完了,会从头开始读。 - 潜在问题 :如果这是登录接口,重复使用同一个
code可能会失败(因为code通常是一次性的)。请确保你的codes.csv数据量足够大,或者生成code的机制支持重复使用。
4. 关键配置细节审查
- 关于 BeanShell 的性能隐患
目前使用了 BeanShell PostProcessor 来设置属性。
-
问题:BeanShell 在 JMeter 中性能较差,且在高并发下会消耗大量 CPU。
-
替代方案 :如果你只是为了传参,推荐使用 __setProperty 函数直接写在登录请求的"参数"或"Body"里(虽然不美观),或者使用 JSR223 PostProcessor 并选择 Groovy 语言。
-
Groovy 写法:
groovy// 将变量 token 的值存入全局属性 props.put("token", vars.get("token"))
- 关于 Header Manager 的引用(图 10)
目前使用了 bearer ${__P(token)}。
${__P(token)}是读取 JMeter 属性的标准函数。- 如果你的 BeanShell 脚本执行成功,这里就能正确拿到值。
- 调试技巧 :如果发现请求 401 未授权,请检查 BeanShell 是否执行成功,或者查看日志(
jmeter.log)。
5. 整体流程逻辑梳理
根据截图,你的脚本逻辑如下:
- 准备阶段 :从 CSV 读取
code。 - 登录接口 (IAM认证登录) :POST 请求,发送 JSON
{"code": "${code}"}。现使用挡板机制,另外启动服务器拦截请求,返回固定结果token。详见后面第六部分:技巧。 - 提取 :使用 JSON Extractor 提取响应中的
$.value赋值给变量token。 - 转换 :使用 BeanShell 将
token变量转为全局属性token。 - 业务请求 1 (查看当前用户信息) :GET 请求,Header 中携带
bearer ${__P(token)}。 - 业务请求 2 & 3:获取菜单、系统信息(复用上述 token)。
- 压力控制:使用 Stepping Thread Group 控制并发节奏。
6. 下一步建议
下面是 "查看结果树"和"聚合报告"。在性能测试中,这两步是分析结果的关键。重点分析:
- 聚合报告的指标解读(TPS、90% Line、错误率)。
- 查看结果树中的断言(是否有验证响应内容)。
- 监听器的性能影响(在大规模压测时是否应该禁用"查看结果树")。
第二步:结果分析(监听器详解)
你添加了非常专业的监听器,但在截图中它们都是空的(Waiting for samples...)。这是因为你还没有运行或者没有产生数据。
以下是针对你截图中监听器的专业解读:
1. 查看结果树
- 作用:调试脚本的神器。用来查看请求是否发送成功,响应数据是否符合预期。
- 进阶用法 :
- 断言 :在目前的脚本中还没有添加断言。建议右键点击 HTTP 请求 -> 添加 -> 断言 -> 响应断言。比如检查响应文本中是否包含
"code":200。 - 注意 :在进行大规模压测(如 100+ 并发)时,必须禁用"查看结果树",因为它极其消耗内存,会导致 JMeter 崩溃。
- 断言 :在目前的脚本中还没有添加断言。建议右键点击 HTTP 请求 -> 添加 -> 断言 -> 响应断言。比如检查响应文本中是否包含
2. jp@gc - Active Threads Over Time
- 作用:展示随时间变化的活跃线程数。
- 解读:结合你之前的"阶梯线程组"配置,这个图表应该画出一个阶梯状上升的折线图。如果线条是平的,说明线程没有按预期启动;如果线条突然掉到底,说明有线程报错退出了。

3. jp@gc - Response Times Over Time
- 作用:展示随时间变化的响应时间趋势。
- 解读 :这是判断系统稳定性的关键。
- 理想状态:线条平稳,没有剧烈波动。
- 异常状态:随着并发增加(对应上面的阶梯图),如果响应时间呈指数级上升(陡峭变高),说明系统达到了瓶颈,处理不过来了。

4. jp@gc - Transactions per Second
- 作用:每秒事务数/吞吐量,即 QPS。
- 解读 :
- 拐点 :随着并发增加,QPS 会上升。当 QPS 不再随并发增加而上升,甚至开始下降时,那个点就是系统的最大吞吐量。

5. 聚合报告
- 作用:最终的汇总成绩单。
- 关键指标 :
- Samples:总请求数。
- Average:平均响应时间。
- 90% Line:90% 的请求都小于这个时间(比平均值更有参考价值)。
- Error %:错误率。压测中这个必须为 0%,否则测试结果无效。
- Throughput:吞吐量,这是衡量服务器性能最核心的指标。

第四部分:脚本优化
你的脚本逻辑是通的,但为了适应 Java 8 环境和更高并发,有以下 3 点优化建议:
1. 替换 BeanShell(性能优化)
在之前的截图中,你使用了 BeanShell PostProcessor 来设置全局属性。
-
问题:BeanShell 在 JMeter 中性能较差,且在高并发下会成为瓶颈。
-
优化 :既然你用的是 JMeter 5.6.2,建议改用 JSR223 PostProcessor + Groovy 语言。
-
代码修改:
groovy// 替换掉 BeanShell 中的代码 props.put("token", vars.get("token"));解释:
props代表 JMeter 属性(全局),vars代表变量(局部)。这行代码效果和你的 BeanShell 一样,但速度快得多。
2. 完善跨线程组取值
你在 Header Manager 中使用了 ${__P(token)}。
- 确认 :
__P函数用于读取属性。这完全正确。 - 备选 :也可以使用
${__property(token)},效果一样。 - 注意 :确保登录请求在业务请求之前执行完毕。你的"阶梯线程组"配置中,如果所有请求都在同一个组里,要确保登录只执行一次或者在循环外。看你的结构,似乎是把登录和业务放在了同一个线程组里?
- 如果是这样,要注意 CSV Data Set 的"共享模式"。如果是
All threads,多个线程可能会抢到同一行账号数据。
- 如果是这样,要注意 CSV Data Set 的"共享模式"。如果是
3. 添加事务控制器
你的截图里,一个业务操作(如"获取用户信息")可能包含多个请求。
- 建议 :右键线程组 -> 添加 -> 逻辑控制器 -> 事务控制器。
- 操作:把相关的 HTTP 请求拖进去,勾选"Generate parent sample"。
- 效果:聚合报告里会把这一组请求合并成一条记录,显示整个业务流程的总耗时,而不是单个接口的耗时。
第五部分:执行与验证步骤
现在你可以运行测试了,请按以下步骤操作:
- 清理环境 :点击工具栏上的扫帚图标(清理),清空上一次的结果。
- 运行 :点击绿色的启动按钮。
- 观察:
- 先看 jp@gc - Active Threads,确认线程数是否按你预期的阶梯(10, 20, 30...)在增加。
- 再看 jp@gc - Response Times,看响应时间是否有剧烈抖动。
- 停止:测试运行完后,点击停止。
- 分析 :查看 聚合报告 中的 Throughput 和 Error %。
总结:
你的脚本结构已经具备了中级水平(参数化+关联+跨线程/全局变量)。接下来的重点就是运行它,并学会看懂那几个图表。如果运行中遇到报错(比如 401 Unauthorized),通常是 Token 没取对或者没传过去,那时候再回来检查 JSON Extractor 和 Header Manager 即可。
第六部分:技巧
1. 挡板机制
在性能测试中,调用第三方外部接口是个"大坑"。这些接口不受你控制,可能不稳定、有频率限制,甚至还要额外付费。
为了屏蔽这些"外部不确定因素",通常需要引入挡板机制(Service Virtualization / Mocking)。这就好比拍电影时,不直接去外太空,而是在绿幕前拍摄,后期再合成背景。
以下是几种主流且实用的挡板方案,按推荐程度排序:
①🧪 使用 WireMock(最推荐,轻量级)
WireMock 是目前最流行的 HTTP Mock 服务工具,它独立于 JMeter 运行,专门用来模拟 HTTP 接口。
原理
你在本地或测试环境启动一个 WireMock 服务。JMeter 不直接调用第三方接口,而是调用 WireMock。WireMock 收到请求后,根据预设规则返回一个"假"的固定响应。
操作步骤
-
下载 :下载
wiremock-standalone.jar。 -
启动 :在命令行运行
java -jar wiremock-standalone.jar。 -
编写桩(Stub):告诉 WireMock 当收到特定请求时返回什么。
- 例如,创建一个 JSON 文件
mock-response.json:
json
{
"request": {
"method": "GET",
"url": "/api/v1/external-system"
},
"response": {
"status": 200,
"body": "{\"code\": 200, \"data\": \"Mocked Success\"}",
"headers": {
"Content-Type": "application/json"
},
"fixedDelayMilliseconds": 50 // 模拟网络延迟,更真实
}
}
-
加载配置 :将上述文件放入 WireMock 的
mappings目录。 -
JMeter 修改:
- 使用"HTTP 请求默认值"或"HTTP 信息头管理器"。
- 将原本指向第三方域名的请求,修改为指向
localhost:8080(WireMock 默认端口)。
②⚙️ JMeter 内部实现 Mock(无需额外工具)
如果你不想部署额外服务,可以直接在 JMeter 内部"截胡"请求。
JSR223 Sampler :模拟 HTTP 服务是一个非常灵活且强大的方案。它允许你编写 Groovy 脚本来启动一个轻量级的嵌入式服务器(通常使用 NanoHTTPD 或 Java 原生 Socket),从而完全控制返回的响应内容、状态码和延迟,非常适合做挡板(Mock)测试。
HTTP Mirror Server:在 JMeter 的官方组件中,更加如何"开箱即用"特性的组件,它可以原封不动地返回请求内容,适合快速验证请求格式。
下面介绍,高阶的 JSR223 自定义挡板 (满足你"返回固定结果"的需求)和 原生的 HTTP Mirror Server(用于快速调试)。
方案一:使用 JSR223 Sampler 模拟 HTTP 服务(最灵活)
这种方式不需要依赖外部工具,直接在 JMeter 内部运行一个微型服务。我们需要使用 Groovy 脚本结合 NanoHTTPD(JMeter 自带或需引入 jar 包)或者简单的 Socket 来实现。
注意 :JMeter 的 Sampler 是"客户端"角色,要把它变成"服务端",通常是在 setUp Thread Group 中启动一个后台线程来监听端口。
- 实现步骤

启动脚本(放在 setUp Thread Group 的 JSR223 Sampler 中):
groovy
import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpExchange;
import java.io.OutputStream;
import java.net.InetSocketAddress;
// 1. 配置端口和返回内容
int port = 48001;
String fixedResponse = "{\"header\": {\"code\": 10000000}, \"body\": {\"value\": \"eyJhbGc\"}}";
// 2. 创建服务器实例
// 注意:这里使用 InetSocketAddress 绑定到 0.0.0.0 以允许外部访问,或者 "localhost" 仅限本地
HttpServer server = HttpServer.create(new InetSocketAddress(port), 0);
// 3. 定义处理逻辑 (上下文路径为 /)
server.createContext("/", new HttpHandler() {
@Override
public void handle(HttpExchange exchange) throws IOException {
// 获取请求方法 (GET, POST 等)
String method = exchange.getRequestMethod();
// log.info("Mock Server received: " + method + " " + exchange.getRequestURI());
// 设置响应头
exchange.getResponseHeaders().add("Content-Type", "application/json");
// 将响应字符串转换为字节流
byte[] responseBytes = fixedResponse.getBytes("UTF-8");
// 发送响应头 (状态码 200, 内容长度)
exchange.sendResponseHeaders(200, responseBytes.length);
// 发送响应体
OutputStream os = exchange.getResponseBody();
os.write(responseBytes);
os.close();
}
});
// 4. 启动服务器
// 使用单线程 executor,或者根据需要设置线程池
server.setExecutor(null);
server.start();
log.info(">>> Mock HTTP Server started on port: " + port);
// 5. 将 server 对象存入 props,以便在测试结束后关闭它
props.put("mockServer", server);
- 验证方案
启动上述脚本后,你的"挡板"就已经在 48001 端口运行了。
- 添加 HTTP 请求:在测试计划中添加一个普通的 HTTP 请求采样器。
- 配置目标:
- 服务器名称或IP :
localhost - 端口号 :
48001 - 协议 :
http
- 运行测试:运行测试计划,查看"查看结果树"。
- 预期结果 :你应该能看到请求成功,且响应数据正是你在脚本中写死的
{"code": 10000000,...}。
方案二:使用 JMeter 原生 HTTP Mirror Server(最简单)
如果你不需要复杂的逻辑判断,只是想看看发出的请求长什么样,或者需要一个简单的回显服务,JMeter 自带的 HTTP Mirror Server 是最快的选择。它会将接收到的请求原封不动地作为响应返回。
-
启动步骤
-
在 JMeter 界面中,右键点击 "工作台" (WorkBench) 或 "测试计划"。
-
选择 "非测试元件" (Non-Test Elements) -> "HTTP 镜像服务器" (HTTP Mirror Server)。
-
配置组件:
- 端口 : 默认
8081(如果端口被占用,请修改)。 - Max number of Threads: 线程数,可根据需要设置。
-
启动服务 :点击组件面板上的 "启动" (Start) 按钮。
-
验证与使用
启动后,该服务器就在本地 8081 端口监听。
- 修改采样器 :将你现有的 HTTP 请求采样器的"服务器名称或 IP"改为
localhost,端口改为8081。 - 运行并查看 :
- 运行测试。
- 添加 "查看结果树" 监听器。
- 点击具体的请求,查看 "响应数据" 标签页。
- 结果分析:你会发现响应数据里包含了你刚才发出的请求头、请求体等所有信息。这非常适合用来调试"我的请求头是否设置正确"、"Cookie 是否带上"等问题。
方案对比总结
对比
| 特性 | JSR223 Sampler (自定义脚本) | HTTP Mirror Server (原生组件) |
|---|---|---|
| 灵活性 | ⭐⭐⭐⭐⭐ (极高,可写代码控制逻辑) | ⭐ (低,仅回显请求) |
| 返回内容 | 可返回固定结果、动态数据或根据请求参数返回不同结果 | 只能原样返回接收到的请求内容 |
| 实现难度 | 中等 (需要编写 Groovy/Java 代码) | 简单 (拖拽组件,点击启动) |
| 适用场景 | 模拟第三方接口挡板、模拟超时/错误码、复杂业务逻辑解耦 | 调试请求格式、验证 HTTP 头/签名是否正确 |
📌 总结建议
如果目标是"返回固定结果 ",方案一(JSR223) 是最佳选择。虽然代码稍微多一点,但它能让你完全掌控挡板的行为,比如模拟接口超时(在脚本里 Thread.sleep())或模拟 500 错误。。
③🛡️ 使用 Nginx 反向代理(运维级方案)
如果你的测试环境有 Nginx,这是最优雅的方案,完全对 JMeter 透明。
原理
在测试环境的 Nginx 配置中,拦截对第三方系统的请求,直接返回本地文件。
配置示例
nginx
location /api/external/ {
# 屏蔽真实调用,直接返回本地文件
default_type application/json;
return 200 '{"code":200, "msg":"Nginx Mock Success"}';
# 或者返回一个静态文件
# alias /path/to/mock/data.json;
}
优点
- JMeter 脚本完全不用改,还是调原来的域名。
- 所有调用该环境的流量都被 Mock,干净彻底。
④📊 方案对比与总结
方案对比
| 方案 | 实施难度 | 灵活性 | 适用场景 |
|---|---|---|---|
| WireMock | ⭐⭐ | ⭐⭐⭐⭐⭐ | 最推荐。适合需要模拟复杂逻辑、不同返回码、网络延迟的场景。 |
| Nginx Mock | ⭐⭐⭐ | ⭐⭐⭐ | 适合运维配合,不想改动 JMeter 脚本,且是固定返回值的场景。 |
| JMeter 内部 Mock | ⭐⭐ | ⭐⭐ | 适合临时调试,或者不想依赖外部工具的小型测试。 |
📌 总结:实战步骤
建议采用 WireMock 方案:
- 启动 WireMock,模拟那个"外部系统"的接口,设定延迟 100ms,返回成功 JSON。
- 修改 JMeter:
- 在"HTTP 请求默认值"里,或者针对那个特定的 HTTP 请求,把"服务器名称或 IP"改成 WireMock 的地址。
- 或者,更高级一点,使用 JMeter 的"CSV 数据文件设置"来控制域名:
- CSV 内容:
ENV,DOMAIN->TEST,real-api.com/MOCK,localhost:8080 - HTTP 请求里填
${DOMAIN}。
- CSV 内容:
- 这样,你只需切换 CSV 里的参数,就能在"真实调用"和"挡板模式"之间随意切换。