JMeter 从入门到精通
适合人群:测试工程师、后端开发工程师、DevOps 工程师
本文从 JMeter 基础概念讲起,覆盖脚本编写、参数化、关联、分布式压测、CI/CD 集成等进阶内容,带你从零掌握性能测试利器。
目录
- 1. JMeter 简介
- 2. 环境搭建
- 3. JMeter 核心概念与组件体系
- 4. 第一个测试计划
- 5. 常用采样器详解
- 6. 配置元件
- 7. 前置处理器与后置处理器
- 8. 断言
- 9. 定时器
- 10. 逻辑控制器
- 11. 监听器与结果分析
- 12. 参数化
- 13. 关联与动态数据处理
- 14. 性能测试实战完整流程
- 15. 命令行(非 GUI)模式
- 16. 分布式压测
- 17. 性能指标分析与调优
- 18. JMeter 插件
- 19. CI/CD 集成(Jenkins)
- 20. 最佳实践与常见问题
1. JMeter 简介
1.1 什么是 JMeter
Apache JMeter 是 Apache 组织开发的开源性能测试工具,最初用于 Web 应用程序的负载测试,如今已扩展支持多种协议和应用程序类型。
1.2 JMeter 的核心特点
- 开源免费:基于 Java 开发,100% 免费开源
- 跨平台:支持 Windows、Linux、macOS
- 多协议支持:HTTP/HTTPS、FTP、JDBC、JMS、SMTP、TCP 等
- GUI + 非 GUI 双模式:GUI 用于脚本编写调试,非 GUI 用于大规模压测
- 可视化测试报告:自带丰富的图表和报表
- 可扩展:支持自定义插件、BeanShell/Groovy 脚本
- 分布式测试:支持多机协同压测,轻松模拟海量并发
1.3 JMeter 能做什么
| 测试类型 | 说明 |
|---|---|
| 负载测试 | 模拟预期用户量,验证系统在正常/峰值负载下的表现 |
| 压力测试 | 不断增加负载,找出系统的崩溃点 |
| 稳定性测试 | 长时间运行,验证系统是否存在内存泄漏等问题 |
| 性能基准测试 | 建立性能基线,用于版本对比 |
| 接口功能测试 | 验证接口的正确性 |
1.4 JMeter vs 其他性能测试工具
| 特性 | JMeter | LoadRunner | Gatling | Locust |
|---|---|---|---|---|
| 开源 | ✅ | ❌(商业) | ✅ | ✅ |
| GUI | ✅ | ✅ | ❌(DSL) | ❌(Python) |
| 分布式 | ✅ | ✅ | ✅ | ✅ |
| 协议支持 | 丰富 | 非常丰富 | HTTP 为主 | HTTP 为主 |
| 学习曲线 | 中等 | 较陡 | 较陡 | 低 |
| 报告 | 良好 | 优秀 | 优秀 | 一般 |
2. 环境搭建
2.1 前置条件
JMeter 基于 Java 开发,需要先安装 JDK。
# 检查 JDK 是否已安装
java -version
# JMeter 5.6+ 推荐 JDK 11 或 JDK 17
# 如果未安装,Mac 可使用 Homebrew
brew install openjdk@17
# 配置 JAVA_HOME(Mac)
echo 'export JAVA_HOME=$(/usr/libexec/java_home -v 17)' >> ~/.zshrc
echo 'export PATH=$JAVA_HOME/bin:$PATH' >> ~/.zshrc
source ~/.zshrc
# 验证
javac -version
2.2 下载安装 JMeter
# 方式一:官网下载
# 访问 https://jmeter.apache.org/download_jmeter.cgi 下载最新版本
# 方式二:命令行下载(以 5.6.3 为例)
cd /usr/local
wget https://archive.apache.org/dist/jmeter/binaries/apache-jmeter-5.6.3.tgz
tar -xzf apache-jmeter-5.6.3.tgz
# 配置环境变量
echo 'export JMETER_HOME=/usr/local/apache-jmeter-5.6.3' >> ~/.zshrc
echo 'export PATH=$JMETER_HOME/bin:$PATH' >> ~/.zshrc
source ~/.zshrc
# 验证安装
jmeter --version
2.3 Docker 安装(可选)
# 使用官方镜像快速启动 GUI 模式
docker run -d \
--name jmeter \
-p 5900:5900 \
-v /Users/wanghao/Desktop/study/jmeter-scripts:/jmeter \
justb4/jmeter:latest
2.4 启动 JMeter
# GUI 模式(用于脚本编写和调试)
jmeter
# 非 GUI 模式(用于执行压测,推荐)
jmeter -n -t test_plan.jmx -l result.jtl -e -o report/
# Mac 上也可以直接双击 JMETER_HOME/bin/jmeter.sh
⚠️ 重要提示:GUI 模式消耗资源大,仅用于编写和调试脚本。正式执行压测务必使用非 GUI 模式,否则 GUI 本身会成为瓶颈,导致测试结果不准确。
3. JMeter 核心概念与组件体系
3.1 JMeter 的组件分类
JMeter 的测试脚本由若干"元件(Element)"组成,按功能分为以下几大类:
┌─────────────────────────────────────────────────────────┐
│ 测试计划 (Test Plan) │
│ 线程组 (Thread Group) │
│ ├── 采样器 (Sampler) → 发送请求 │
│ ├── 逻辑控制器 (Logic Controller) → 控制执行流程 │
│ ├── 配置元件 (Config Element) → 提供默认值、变量 │
│ ├── 前置处理器 (Pre-Processor) → 请求前处理 │
│ ├── 后置处理器 (Post-Processor) → 请求后处理(提取数据) │
│ ├── 断言 (Assertion) → 验证响应结果 │
│ ├── 定时器 (Timer) → 控制请求间隔 │
│ └── 监听器 (Listener) → 收集展示结果 │
└─────────────────────────────────────────────────────────┘
3.2 元件的执行顺序
JMeter 中同一作用域下元件的执行顺序是固定的(无论在树中的排列顺序如何):
1. 配置元件 (Config Element)
2. 前置处理器 (Pre-Processor)
3. 定时器 (Timer)
4. 采样器 (Sampler)
5. 后置处理器 (Post-Processor)
6. 断言 (Assertion)
7. 监听器 (Listener)
💡 记忆技巧:配置 → 前置 → 定时 → 采样 → 后置 → 断言 → 监听。理解执行顺序对编写复杂脚本至关重要。
3.3 作用域规则
元件的作用域由它在测试树中的层级位置决定:
- 采样器:不与任何其他元件通信,它本身就是请求
- 逻辑控制器:影响其子节点的执行
- 其他元件(配置、断言、定时器、前后置处理器、监听器):作用于其同级或父级的采样器
Thread Group
├── HTTP Request A ← Sampler
├── Response Assertion ← 作用于 A(同级)
├── View Results Tree ← 作用于 A 和 B(同级所有采样器)
├── HTTP Request B ← Sampler
│ ├── JSON Extractor ← 仅作用于 B(B 的子节点,父级是 B)
│ └── Constant Timer ← 仅作用于 B
└── HTTP Request C ← Sampler
3.4 变量与属性
| 概念 | 作用范围 | 线程间共享 | 设置方式 |
|---|---|---|---|
| 变量 (Variables) | 当前线程 | ❌ 不共享 | ${varName} |
| 属性 (Properties) | 全局 | ✅ 共享 | ${__P(propName)} 或 ${__property(propName)} |
# 命令行传参使用属性
jmeter -n -t test.jmx -JthreadNum=100 -JrampUp=10
在脚本中引用:
线程数: ${__P(threadNum)}
Ramp-Up: ${__P(rampUp)}
4. 第一个测试计划
4.1 创建测试计划
- 启动 JMeter GUI
- 默认有一个 Test Plan 节点,重命名为
MyFirstTest - 右键 Test Plan → Add → Threads (Users) → Thread Group
4.2 配置线程组
线程组是 JMeter 模拟虚拟用户的核心:
| 参数 | 说明 | 示例 |
|---|---|---|
| Number of Threads | 虚拟用户数 | 100 |
| Ramp-Up Period (seconds) | 启动所有线程所需时间 | 10(每秒启动10个用户) |
| Loop Count | 每个线程循环次数 | 10(0表示无限) |
| Same user on each iteration | 每次迭代是否复用同一用户 | 默认勾选 |
💡 Ramp-Up 理解:如果线程数=100,Ramp-Up=10秒,那么每秒会启动 10 个线程,10秒后全部启动完毕。这模拟了用户逐渐上线的真实场景。
4.3 添加 HTTP 请求
右键 Thread Group → Add → Sampler → HTTP Request:
| 字段 | 值 |
|---|---|
| Name | 访问首页 |
| Protocol | http |
| Server Name | www.example.com |
| Path | / |
4.4 添加结果监听器
右键 Thread Group → Add → Listener → View Results Tree
4.5 运行测试
点击工具栏绿色 ▶ 按钮运行,在 View Results Tree 中查看结果。
4.6 测试计划 XML 结构
保存后的 .jmx 文件本质是 XML,核心结构如下:
<?xml version="1.0" encoding="UTF-8"?>
<jmeterTestPlan version="1.2" properties="5.0">
<hashTree>
<TestPlan guiclass="TestPlanGui" testname="MyFirstTest">
<stringProp name="TestPlan.comments">第一个测试计划</stringProp>
<boolProp name="TestPlan.functional_mode">false</boolProp>
</TestPlan>
<hashTree>
<ThreadGroup guiclass="ThreadGroupGui" testname="用户组">
<intProp name="ThreadGroup.num_threads">100</intProp>
<stringProp name="ThreadGroup.ramp_time">10</stringProp>
<intProp name="LoopController.loops">10</intProp>
</ThreadGroup>
<hashTree>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testname="访问首页">
<stringProp name="HTTPSampler.domain">www.example.com</stringProp>
<stringProp name="HTTPSampler.path">/</stringProp>
</HTTPSamplerProxy>
<hashTree/>
</hashTree>
</hashTree>
</hashTree>
</jmeterTestPlan>
5. 常用采样器详解
5.1 HTTP Request 采样器
最常用的采样器,用于发送 HTTP/HTTPS 请求。
核心配置项:
Protocol: http / https
Server Name or IP: api.example.com
Port Number: 8080
Method: GET / POST / PUT / DELETE / PATCH
Path: /api/v1/users
Content Encoding: UTF-8
POST 请求传参方式:
- Parameters 方式(form-data / x-www-form-urlencoded)
name=admin&age=25
- Body Data 方式(JSON)
{
"username": "admin",
"password": "123456",
"email": "admin@example.com"
}
上传文件:
在 HTTP Request 中勾选 Use multipart/form-data,在 Files Upload 标签页添加:
File Path: /path/to/test.png
Parameter Name: file
MIME Type: image/png
Advanced 配置(重要):
| 参数 | 说明 |
|---|---|
| Redirect Automatically | 自动跟随重定向(不记录重定向请求) |
| Follow Redirects | 跟随重定向(会记录重定向请求,推荐) |
| Use KeepAlive | 使用持久连接 |
| Source Address | 指定请求的源 IP(多网卡场景) |
| Timeout (ms) | 连接/读取超时时间 |
5.2 JDBC Request 采样器
用于直接对数据库执行 SQL,常用于测试数据库性能或准备测试数据。
前置准备——配置 JDBC Connection Configuration:
| 参数 | 值 |
|---|---|
| Variable Name Bound to Pool | mysql_conn |
| Database URL | jdbc:mysql://localhost:3306/testdb |
| JDBC Driver class | com.mysql.cj.jdbc.Driver |
| Username | root |
| Password | 123456 |
⚠️ 需要将 MySQL JDBC 驱动 jar 包放入
$JMETER_HOME/lib/目录。
JDBC Request 配置:
Variable Name: mysql_conn
Query Type: Select Statement
Query:
SELECT id, username, email FROM users WHERE id = ${user_id}
参数化查询(Parameter Values):
Query Type: Prepared Select Statement
Query: SELECT * FROM users WHERE age > ? AND status = ?
Parameter values: 18, active
Parameter types: INTEGER, VARCHAR
5.3 Debug Sampler
不发送实际请求,用于输出变量和属性的值,调试时非常有用。
JMeter Properties: False
JMeter Variables: True
System Properties: False
💡 配合 View Results Tree 的 Response Data 可以查看所有变量的当前值,是调试关联变量时的利器。
5.4 其他常用采样器一览
| 采样器 | 用途 |
|---|---|
| FTP Request | 测试 FTP 服务器 |
| SMTP Sampler | 发送邮件测试 |
| TCP Sampler | 测试 TCP 协议 |
| JMS Publisher/Subscriber | 测试消息队列 |
| BeanShell/Groovy Script | 执行自定义脚本 |
| OS Process Sampler | 执行操作系统命令 |
| JSR223 Sampler | 支持多种脚本语言的高级采样器 |
6. 配置元件
6.1 HTTP Request Defaults
为同作用域下所有 HTTP 采样器设置默认值,避免重复配置。
Protocol: https
Server Name: api.example.com
Port: 443
Content Encoding: UTF-8
当多个请求都访问同一服务器时,统一配置请求默认值,后续采样器只需填写 Path 即可。
6.2 HTTP Header Manager
管理请求头信息:
| Name | Value |
|---|---|
| Content-Type | application/json |
| Authorization | Bearer ${token} |
| Accept-Language | zh-CN |
| User-Agent | Mozilla/5.0 |
6.3 HTTP Cookie Manager
自动管理 Cookie,模拟浏览器行为:
- Clear cookies each iteration:每次迭代清除 Cookie(模拟新用户登录)
- Store HTTP cookies:存储并自动携带 Cookie
💡 测试需要登录的接口时,Cookie Manager 可以自动保存登录后的 Session Cookie,后续请求自动携带,无需手动处理。
6.4 User Defined Variables
定义全局变量:
| Name | Value |
|---|---|
| HOST | api.example.com |
| PORT | 443 |
| PROTOCOL | https |
| USERNAME | testuser |
引用方式:${HOST}
6.5 CSV Data Set Config
从 CSV 文件读取参数化数据,是最常用的参数化方式(详见 第12章 参数化)。
7. 前置处理器与后置处理器
7.1 前置处理器(Pre-Processor)
在采样器发送请求之前执行。
7.1.1 User Parameters
为每个线程/每次迭代设置不同参数:
| Name | Thread 1 | Thread 2 | Thread 3 |
|---|---|---|---|
| username | user1 | user2 | user3 |
| password | pass1 | pass2 | pass3 |
7.1.2 JSR223 PreProcessor(推荐)
使用 Groovy 在请求前执行逻辑:
// 生成随机时间戳作为请求参数
def timestamp = System.currentTimeMillis()
vars.put("timestamp", timestamp.toString())
// 生成 MD5 签名
def sign = "key=" + vars.get("apiKey") + "×tamp=" + timestamp
def md5 = java.security.MessageDigest.getInstance("MD5")
def bytes = md5.digest(sign.getBytes("UTF-8"))
def hexString = new BigInteger(1, bytes).toString(16).padLeft(32, '0')
vars.put("sign", hexString)
log.info("Generated sign: " + hexString)
💡 为什么用 Groovy 而不是 BeanShell? Groovy 性能更好(编译缓存),语法兼容 Java,且是 JMeter 官方推荐脚本语言。请始终在 JSR223 元件中勾选
Cache compiled script。
7.2 后置处理器(Post-Processor)
在采样器收到响应之后执行,主要用于提取数据。
7.2.1 Regular Expression Extractor(正则提取)
| 字段 | 值 |
|---|---|
| Reference Name | token |
| Regular Expression | “token”:“(.+?)” |
| Template | 1 1 1 |
| Match No. | 1 |
| Default Value | NOT_FOUND |
执行后可通过 ${token} 引用提取的值。
正则表达式说明:
():捕获组,$1$表示取第一个捕获组.+?:非贪婪匹配,匹配任意字符至少一次Match No.:0=随机,1=第一个匹配,-1=所有匹配(生成数组token_1,token_2…)
7.2.2 JSON Extractor
针对 JSON 响应,使用 JSONPath 提取:
{
"code": 200,
"data": {
"userId": 10086,
"token": "abc123xyz",
"roles": ["admin", "user"]
}
}
| 字段 | 值 |
|---|---|
| Name of created variable | userId |
| JSON Path | $.data.userId |
| Match No. | 1 |
| Default Value | NOT_FOUND |
常用 JSONPath 语法:
| 表达式 | 说明 | 示例 |
|---|---|---|
$.key | 根对象的属性 | $.code → 200 |
$.a.b | 嵌套属性 | $.data.token → abc123xyz |
$.list[0] | 数组第一个元素 | $.data.roles[0] → admin |
$.list[-1] | 数组最后一个元素 | $.data.roles[-1] → user |
$.list[0:2] | 数组切片 | 前两个元素 |
$.list[?(@.age>18)] | 条件过滤 | 过滤 age>18 的对象 |
$..key | 递归搜索所有层级 | 找到所有 token |
7.2.3 Boundary Extractor(边界提取器)
比正则更简单高效的提取方式,适合提取固定边界的内容:
| 字段 | 值 |
|---|---|
| Reference Name | sessionId |
| Left Boundary | sessionId=" |
| Right Boundary | " |
| Match No. | 1 |
| Default Value | NOT_FOUND |
7.2.4 JSR223 PostProcessor
最灵活的后置处理方式:
import groovy.json.JsonSlurper
// 解析响应
def jsonSlurper = new JsonSlurper()
def response = jsonSlurper.parseText(prev.getResponseDataAsString())
// 提取并设置变量
if (response.code == 200) {
vars.put("userId", response.data.userId.toString())
vars.put("token", response.data.token)
log.info("Login success, userId: ${response.data.userId}")
} else {
log.error("Login failed: ${response.message}")
prev.setSuccessful(false) // 标记请求失败
}
💡
prev是内置变量,代表当前 SampleResult 对象,可以获取响应数据、响应码、响应时间等信息。
8. 断言
断言用于验证响应是否符合预期,如果断言失败,该采样器会被标记为失败。
8.1 Response Assertion(响应断言)
| 字段 | 配置 |
|---|---|
| Apply to | Main sample only |
| Field to Test | Response Code / Response Body |
| Pattern Matching Rules | Contains / Equals / Matches |
| Patterns to Test | 200 |
常见用例:
| 验证目标 | Field to Test | Rule | Pattern |
|---|---|---|---|
| HTTP状态码 | Response Code | Equals | 200 |
| 响应包含关键词 | Text Response | Contains | success |
| 响应为指定JSON | Text Response | Equals | {“code”:200} |
| 正则匹配 | Text Response | Matches | .“status”:“ok”. |
8.2 JSON Assertion
专门针对 JSON 响应的断言:
Assert JSON Path exists: $.code
Additionally assert value: 勾选
Expected value: 200
8.3 Duration Assertion
验证响应时间是否在预期范围内:
Duration in milliseconds: 2000 (超过2秒则断言失败)
8.4 Size Assertion
验证响应体大小:
Size in bytes: 1024
Compare: > (大于1024字节)
8.5 JSR223 Assertion
最灵活的断言,可编写复杂逻辑:
import groovy.json.JsonSlurper
def jsonSlurper = new JsonSlurper()
def response = jsonSlurper.parseText(prev.getResponseDataAsString())
// 复杂断言逻辑
if (response.code != 200) {
AssertionResult.setFailure(true)
AssertionResult.setFailureMessage("期望code=200,实际code=${response.code}")
} else if (response.data.list.size() == 0) {
AssertionResult.setFailure(true)
AssertionResult.setFailureMessage("返回列表为空")
} else if (response.data.list[0].price < 0) {
AssertionResult.setFailure(true)
AssertionResult.setFailureMessage("商品价格不能为负数")
}
8.6 断言结果查看
添加 Assertion Results 监听器,可以查看哪些请求断言失败及失败原因。
9. 定时器
定时器控制请求之间的间隔,模拟真实用户的思考时间。
⚠️ 定时器作用于其作用域内的所有采样器,且在采样器之前执行。如果只想对某个采样器生效,需将其作为该采样器的子节点。
9.1 Constant Timer(固定定时器)
Thread Delay (ms): 3000 (每个请求间隔3秒)
9.2 Uniform Random Timer(均匀随机定时器)
Random Delay Maximum (ms): 2000
Constant Delay Offset (ms): 1000
实际延迟 = 1000 + Random(0, 2000) = 1000~3000ms
💡 这是最常用的定时器,模拟真实用户操作间的随机停顿。
9.3 Gaussian Random Timer(高斯随机定时器)
Deviation (ms): 1000
Constant Delay Offset (ms): 2000
延迟符合高斯分布,更接近真实用户行为。
9.4 Constant Throughput Timer(常数吞吐量定时器)
控制吞吐量而非延迟:
Target throughput: 60.0 (每分钟60次)
Calculate throughput based on: all active threads in current thread group
💡 用于精确控制 TPS(每秒事务数)。注意这是"尽力达到"目标吞吐量,实际取决于服务器响应速度。
9.5 Precise Throughput Timer(精确吞吐量定时器)
JMeter 5.x 引入,比 Constant Throughput Timer 更精确:
Target throughput: 60.0 (每分钟60次)
Throughput period: 1 (分钟)
10. 逻辑控制器
逻辑控制器控制采样器的执行流程。
10.1 Simple Controller
最简单的控制器,仅用于分组组织,不改变执行逻辑。
10.2 Loop Controller(循环控制器)
Loop Count: 5 (循环执行子节点5次)
Thread Group
├── Login (HTTP)
├── Loop Controller (5次)
│ ├── Search (HTTP)
│ └── View Detail (HTTP)
└── Logout (HTTP)
在线程组中勾选
Same user on each iteration,如果取消勾选则每次迭代会重置线程上下文(模拟不同用户)。
10.3 If Controller(条件控制器)
根据条件决定是否执行子节点:
Condition (default javascript): "${loginStatus}" == "success"
⚠️ 默认使用 JavaScript 解释条件,推荐取消勾选
Interpret Condition as Variable Expression,然后使用 Groovy 语法:Condition: ${__groovy(vars.get('loginStatus') == 'success',)}
10.4 While Controller(循环控制器)
满足条件时持续循环:
Condition: ${__groovy(vars.get('hasMore') != 'false' && vars.get('retryCount').toInteger() < 3,)}
常用于轮询等待某个条件满足。
10.5 ForEach Controller(遍历控制器)
遍历一组变量执行子节点:
Input variable prefix: user
Start index: 0
End index: 5
Output variable name: currentUser
配合正则提取器的 -1(全部匹配)使用,遍历 user_1, user_2…user_5。
10.6 Switch Controller(切换控制器)
根据索引执行特定子节点:
Switch Value: 2 (执行第3个子节点,从0开始)
如果值为变量,可实现动态路由。
10.7 Interleave Controller(交替控制器)
交替执行子节点:
Thread 1 → 子节点1
Thread 2 → 子节点2
Thread 3 → 子节点3
Thread 4 → 子节点1
...
10.8 Once Only Controller(仅一次控制器)
子节点在每个线程中只执行一次(第一次迭代时):
Thread Group
├── Once Only Controller
│ └── Login (HTTP) ← 每个线程只登录一次
├── Loop Controller (10次)
│ └── Query Data (HTTP) ← 查询10次
└── Logout (HTTP)
10.9 Transaction Controller(事务控制器)
将多个采样器组合成一个事务:
Transaction Controller (Generate parent sample: 勾选)
├── Login (HTTP) ← 200ms
├── Search (HTTP) ← 300ms
└── View Detail (HTTP) ← 150ms
事务总耗时 = 200 + 300 + 150 = 650ms,在报告中作为单一事务统计。
💡 事务控制器是衡量"用户完整业务流程"性能的关键。一个完整下单流程(登录→浏览→加购→下单→支付)应作为一个事务统计。
10.10 Critical Section Controller(临界区控制器)
控制并发访问,确保同一时刻只有一个线程执行子节点:
Lock Name: orderLock
用于模拟真实场景中的串行化操作(如库存扣减)。
11. 监听器与结果分析
11.1 View Results Tree(查看结果树)
最常用的调试监听器,展示每个请求的详细信息:
- Sampler results:请求/响应头信息
- Request:发送的请求内容
- Response data:响应体(支持 JSON/HTML/正则/JSONPath/XPath 等多种查看器)
- Assertion result:断言结果
⚠️ 压测时务必禁用或删除 View Results Tree,它会消耗大量内存。仅调试时使用。
11.2 Summary Report(汇总报告)
| Label | # Samples | Average | Min | Max | Std. Dev. | Error % | Throughput | Received KB/sec | Sent KB/sec | Avg. Bytes |
|---|
各指标含义:
- Average:平均响应时间(ms)
- Std. Dev.:标准差,越小越稳定
- Error %:错误率
- Throughput:吞吐量(请求/秒)
- Avg. Bytes:平均响应大小
11.3 Aggregate Report(聚合报告)
类似 Summary Report,但按百分位统计:
| Label | # Samples | Average | Median | 90% Line | 95% Line | 99% Line | Min | Max | Error % | Throughput |
|---|
- Median(50% Line):中位数,50% 的请求响应时间低于此值
- 90% Line:90% 的请求响应时间低于此值
- 95% Line:95% 的请求响应时间低于此值
- 99% Line:99% 的请求响应时间低于此值
💡 关注 P95/P99 而非平均值:平均值会被少量极慢请求拉高或被大量快请求拉低,P95/P99 更能反映大多数用户的真实体验。
11.4 HTML Dashboard Report(HTML 报告)
生成最专业的可视化报告:
jmeter -n -t test_plan.jmx -l result.jtl -e -o ./report/
报告包含:
- Statistics:统计表格
- APDEX:应用性能指数
- Response Times Over Time:响应时间趋势图
- Throughput Over Time:吞吐量趋势图
- Active Threads Over Time:活跃线程趋势图
- Response Time Percentiles:响应时间百分位分布
- Errors:错误统计
APDEX 详解
APDEX(Application Performance Index)是衡量用户满意度的标准指标:
- T(满意阈值):默认 500ms,响应时间 ≤ T 为"满意"
- F(容忍阈值):默认 1500ms,T < 响应时间 ≤ F 为"容忍"
- 响应时间 > F:为"不满意"
APDEX = (满意数 + 容忍数/2) / 总请求数
分数范围 0~1,越接近 1 用户满意度越高。
11.5 Backend Listener(后端监听器)
将结果实时发送到 InfluxDB / Grafana,实现实时监控:
Backend Listener implementation: org.apache.jmeter.visualizers.backend.influxdb.InfluxdbBackendListenerClient
influxdbMetricsSender: org.apache.jmeter.visualizers.backend.influxdb.HttpMetricsSender
influxdbUrl: http://localhost:8086/write?db=jmeter
measurement: jmeter
summaryOnly: false
配合 Grafana 的 JMeter Dashboard 模板,可实时展示压测数据。
12. 参数化
12.1 为什么需要参数化
如果所有虚拟用户发送相同的请求,缓存等机制会使测试结果失真。参数化让每个用户使用不同的数据,模拟真实场景。
12.2 CSV Data Set Config(最常用)
准备数据文件 users.csv:
username,password,email
user1,pass123456,user1@example.com
user2,pass123456,user2@example.com
user3,pass123456,user3@example.com
user4,pass123456,user4@example.com
user5,pass123456,user5@example.com
配置 CSV Data Set Config:
| 参数 | 值 | 说明 |
|---|---|---|
| Filename | /path/to/users.csv | CSV 文件路径 |
| File Encoding | UTF-8 | 文件编码 |
| Variable Names | username,password,email | 变量名(留空则用首行) |
| Ignore first line | True | 忽略首行(表头) |
| Delimiter | , | 分隔符 |
| Allow quoted data? | False | 是否允许引号 |
| Recycle on EOF? | True | 读到文件末尾后是否从头开始 |
| Stop thread on EOF? | False | 读到末尾是否停止线程 |
| Sharing mode | All threads | 共享模式 |
Sharing mode 说明:
| 模式 | 说明 | 适用场景 |
|---|---|---|
| All threads | 所有线程共享,每个线程拿不同数据 | 每个用户用不同账号 |
| Current thread group | 当前线程组内共享 | 多线程组各自独立 |
| Current thread | 每个线程独立读取(每个线程都从第一行开始) | 每个线程遍历所有数据 |
| Identifier | 按线程名分配 | 特殊场景 |
在 HTTP Request 中引用:
POST /api/login
Body:
{
"username": "${username}",
"password": "${password}",
"email": "${email}"
}
12.3 User Parameters
手动指定参数值,适合数据量少的场景:
Name: username
User 1: admin
User 2: testuser
User 3: guest
12.4 函数助手参数化
JMeter 内置丰富的函数,可动态生成数据:
12.4.1 随机数据函数
${__Random(1,100,)} → 1~100的随机整数
${__RandomString(8,abc123,)} → 从abc123中随机取8个字符
${__RandomFromMultipleVars(var1|var2|var3,)} → 从多个变量中随机选一个
12.4.2 字符串函数
${__time(yyyy-MM-dd,)} → 当前日期,如 2025-06-19
${__time(yyyyMMddHHmmss,)} → 时间戳,如 20250619143000
${__time(,)} → 毫秒时间戳
${__UUID()} → 生成UUID
${__threadNum} → 当前线程编号
12.4.3 字符串处理函数
${__split(a,b,c,separator)} → 分割字符串到变量
${__V(var_${counter})} → 嵌套变量引用
${__upperCase(abc,)} → 转大写
${__lowerCase(ABC,)} → 转小写
12.4.4 计数器函数
${__counter(TRUE,)} → 线程独立计数器
${__counter(FALSE,)} → 全局计数器
12.5 使用 __Random 生成唯一数据
// JSR223 中生成唯一手机号
def phone = "138" + String.format("%08d", ${__Random(0,99999999,)})
vars.put("phone", phone)
// 生成随机邮箱
def email = "test_" + System.currentTimeMillis() + "@example.com"
vars.put("email", email)
13. 关联与动态数据处理
13.1 什么是关联
关联是指将前一个请求的响应数据提取出来,作为后一个请求的输入参数。典型场景:
- 登录获取 token → 后续请求携带 token
- 查询列表获取商品ID → 查看商品详情
- 获取验证码 → 提交验证码
13.2 登录 Token 关联实战
Step 1:登录请求
POST /api/login
Body: {"username":"${username}","password":"${password}"}
响应:
{
"code": 200,
"data": {
"token": "eyJhbGciOiJIUzI1NiJ9...",
"userId": 10086
}
}
Step 2:添加 JSON Extractor 提取 token
Variable names: token,userId
JSON Path expressions: $.data.token,$.data.userId
Match No.: 1,1
Default values: NOT_FOUND,NOT_FOUND
Step 3:后续请求携带 token
在 HTTP Header Manager 中:
Authorization: Bearer ${token}
或在 HTTP Request 的 Header 中直接引用 ${token}。
13.3 跨线程组传递变量
默认情况下变量只在当前线程组内有效。跨线程组传递需要使用 __setProperty 和 __P:
线程组 A(登录)——设置属性:
// JSR223 PostProcessor
def token = vars.get("token")
props.put("globalToken", token)
log.info("Set global token: " + token)
线程组 B(业务操作)——获取属性:
# 在 HTTP Header Manager 中引用
Authorization: Bearer ${__P(globalToken,)}
# 或在 JSR223 中获取
def token = props.get("globalToken")
vars.put("token", token)
⚠️ 使用属性传递数据时需注意线程同步问题,确保设置属性的线程组先执行(通过 Test Plan 中的 “Run Thread Groups consecutively” 选项控制)。
13.4 列表数据遍历
场景:查询商品列表,然后逐个查看详情
Step 1:查询列表并提取所有商品 ID
Regular Expression Extractor:
Reference Name: goodsId
Regular Expression: "id":(\d+)
Template: $1$
Match No.: -1 (全部匹配)
此时生成变量:goodsId_1, goodsId_2, … goodsId_N,以及 goodsId_matchNr(总数)
Step 2:ForEach Controller 遍历
Input variable prefix: goodsId
Start index: 1
End index: ${goodsId_matchNr}
Output variable name: currentGoodsId
Step 3:查看详情请求
GET /api/goods/${currentGoodsId}
13.5 动态参数签名(JSR223 实战)
很多接口需要签名验证,使用 JSR223 PreProcessor 生成:
import java.security.MessageDigest
// 获取参数
def apiKey = vars.get("apiKey")
def secret = vars.get("apiSecret")
def timestamp = System.currentTimeMillis().toString()
// 按字典序排序参数
def params = ["apiKey": apiKey, "timestamp": timestamp]
def sortedKeys = params.keySet().sort()
def sb = new StringBuilder()
for (key in sortedKeys) {
sb.append(key).append("=").append(params[key]).append("&")
}
sb.append("secret=").append(secret)
// MD5 签名
def md5 = MessageDigest.getInstance("MD5")
def bytes = md5.digest(sb.toString().getBytes("UTF-8"))
def hex = new BigInteger(1, bytes).toString(16).padLeft(32, '0')
// 设置变量
vars.put("timestamp", timestamp)
vars.put("sign", hex.toUpperCase())
在 HTTP Request 中引用:${timestamp} 和 ${sign}。
14. 性能测试实战完整流程
14.1 测试目标定义
以"电商系统商品查询接口"为例:
| 指标 | 目标 |
|---|---|
| 目标 TPS | ≥ 500 |
| P95 响应时间 | ≤ 200ms |
| P99 响应时间 | ≤ 500ms |
| 错误率 | ≤ 0.1% |
| 持续时间 | 30 分钟 |
14.2 测试场景设计
场景一:基准测试
线程数: 1
Ramp-Up: 1秒
循环: 100次
目的: 获取单用户基准性能
场景二:负载测试
线程数: 100
Ramp-Up: 60秒
循环: 永久
持续时间: 30分钟
目的: 验证正常负载下系统表现
场景三:压力测试(阶梯加压)
使用 Stepping Thread Group 或 Ultimate Thread Group 插件:
阶段1: 0-50用户,持续2分钟
阶段2: 50-100用户,持续2分钟
阶段3: 100-200用户,持续2分钟
阶段4: 200-500用户,持续2分钟
阶段5: 持续500用户10分钟
阶段6: 逐步降到0
场景四:稳定性测试
线程数: 200
Ramp-Up: 60秒
循环: 永久
持续时间: 24小时
目的: 检查内存泄漏、连接泄漏
14.3 完整测试脚本结构
商品查询性能测试
├── User Defined Variables (全局变量)
│ ├── HOST=api.example.com
│ ├── PORT=443
│ └── PROTOCOL=https
├── HTTP Cookie Manager
├── HTTP Request Defaults (默认配置)
├── HTTP Header Manager (默认请求头)
├── setUp Thread Group (数据准备)
│ ├── 登录获取Token
│ └── JSON Extractor (提取token)
├── Thread Group (主测试)
│ ├── CSV Data Set Config (参数化数据)
│ ├── HTTP Header Manager (携带token)
│ ├── Once Only Controller
│ │ └── 用户登录 (每个用户登录一次)
│ ├── Transaction Controller (查询商品事务)
│ │ ├── Uniform Random Timer (思考时间)
│ │ ├── 搜索商品 (HTTP Request)
│ │ │ └── Response Assertion (断言)
│ │ └── 查看商品详情 (HTTP Request)
│ │ └── Duration Assertion (响应时间断言)
│ └── Summary Report
└── tearDown Thread Group (数据清理)
└── 清理测试数据
14.4 执行测试
非 GUI 模式执行(推荐):
# 基本执行
jmeter -n -t product_test.jmx -l result_001.jtl -e -o ./report_001/
# 带参数执行
jmeter -n -t product_test.jmx \
-Jthreads=100 \
-JrampUp=60 \
-Jduration=1800 \
-l result_001.jtl \
-e -o ./report_001/
在脚本中使用 ${__P(threads)} 等引用命令行参数,实现脚本参数化。
14.5 结果分析
打开 report_001/index.html,重点关注:
- APDEX 分数:是否 > 0.85
- Statistics:Error% 是否达标
- Response Times Percentiles:P95/P99 是否达标
- Response Times Over Time:是否有上升趋势(可能内存泄漏)
- Throughput Over Time:TPS 是否稳定
- Errors:错误类型和分布
15. 命令行(非 GUI)模式
15.1 为什么用非 GUI 模式
| 对比项 | GUI 模式 | 非 GUI 模式 |
|---|---|---|
| 资源消耗 | 高(渲染界面) | 低 |
| 结果准确性 | 受 GUI 影响 | 准确 |
| 适用场景 | 脚本编写调试 | 正式压测 |
| 远程执行 | 不支持 | 支持 |
| CI/CD | 不适用 | 适用 |
15.2 常用命令
# 基本执行
jmeter -n -t test.jmx -l result.jtl
# 生成 HTML 报告
jmeter -n -t test.jmx -l result.jtl -e -o ./report/
# 指定运行时长(秒)
jmeter -n -t test.jmx -l result.jtl -Jduration=600 # 10分钟
# 脚本中配合 ${__P(duration)} 使用
# 传参
jmeter -n -t test.jmx -Jthreads=50 -JrampUp=10 -Jloops=100 -l result.jtl
# 远程执行
jmeter -n -t test.jmx -r -l result.jtl
# 指定远程服务器
jmeter -n -t test.jmx -R 192.168.1.10,192.168.1.11 -l result.jtl
# 使用代理
jmeter -n -t test.jmx -H 192.168.1.1 -P 8888 -l result.jtl
# 指定日志级别
jmeter -n -t test.jmx -l result.jtl -L DEBUG
# 指定 properties 文件
jmeter -n -t test.jmx -q user.properties -l result.jtl
15.3 命令行参数速查
| 参数 | 说明 |
|---|---|
-n | 非 GUI 模式 |
-t | 测试计划文件 (.jmx) |
-l | 结果文件 (.jtl) |
-e | 测试结束后生成 HTML 报告 |
-o | HTML 报告输出目录(必须为空或不存在) |
-r | 远程执行所有配置的服务器 |
-R | 指定远程服务器列表 |
-J | 设置 JMeter 属性 |
-G | 设置远程服务器属性 |
-H | 代理服务器 |
-P | 代理端口 |
-q | 额外 properties 文件 |
-L | 日志级别 |
-d | JMeter 主目录 |
-s | 以服务器模式启动(用于分布式) |
-X | 测试结束后停止远程服务器 |
15.4 从已有 .jtl 生成报告
# 如果已有 .jtl 文件,可以单独生成 HTML 报告
jmeter -g result.jtl -o ./report/
15.5 后台执行长时测试
# 后台运行并输出日志
nohup jmeter -n -t test.jmx -l result.jtl -e -o ./report/ > jmeter.log 2>&1 &
# 查看进度
tail -f jmeter.log
# 查看进程
ps aux | grep jmeter
16. 分布式压测
16.1 为什么需要分布式
单台 JMeter 受限于 CPU 和内存,能模拟的并发量有限(通常 1000~2000 线程)。分布式压测通过多台机器协同,可模拟数万甚至数十万并发。
16.2 分布式架构
┌──────────────────┐
│ Master (控制机) │
│ 发送测试计划 │
│ 收集汇总结果 │
└────────┬─────────┘
│
┌──────────────────┼──────────────────┐
│ │ │
┌─────────┴────────┐ ┌───────┴────────┐ ┌───────┴────────┐
│ Slave 1 (执行机) │ │ Slave 2 (执行机)│ │ Slave 3 (执行机)│
│ 1000 线程 │ │ 1000 线程 │ │ 1000 线程 │
└──────────────────┘ └────────────────┘ └────────────────┘
总计: 3000 并发用户
16.3 配置步骤
Step 1:所有机器安装相同版本 JMeter 和 JDK
# 确保版本一致
jmeter --version
java -version
Step 2:配置执行机(Slave)
修改 $JMETER_HOME/bin/jmeter.properties:
# 设置服务器端口(默认 1099)
server.rmi.localport=1099
# 设置 RMI 相关
server.rmi.ssl.disable=true
⚠️ 生产环境建议开启 SSL,这里为演示简化关闭。
启动执行机:
# 在每台 Slave 上执行
jmeter-server
# 或
jmeter -s
Step 3:配置控制机(Master)
修改 $JMETER_HOME/bin/jmeter.properties:
# 填写所有执行机的 IP
remote_hosts=192.168.1.10:1099,192.168.1.11:1099,192.168.1.12:1099
server.rmi.ssl.disable=true
Step 4:启动分布式测试
# 本地全部远程执行
jmeter -n -t test.jmx -r -l result.jtl -e -o ./report/
# 指定部分远程执行
jmeter -n -t test.jmx -R 192.168.1.10,192.168.1.11 -l result.jtl
16.4 线程数计算
关键概念:分布式测试中,Thread Group 中设置的线程数是每台执行机的线程数。
Thread Group 设置: 1000 线程
执行机数量: 3 台
实际总并发: 1000 × 3 = 3000 线程
16.5 分布式测试注意事项
- 防火墙:确保 1099 端口及 RMI 动态端口畅通
- 网络:Master 和 Slave 最好在同一内网
- 时钟同步:所有机器时钟需同步(NTP)
- 数据文件:CSV 数据文件需复制到每台 Slave 相同路径
- 端口冲突:一台机器可启动多个 jmeter-server,需设置不同端口
- 资源监控:压测时监控各 Slave 的 CPU/内存,确保 Slave 本身不是瓶颈
16.6 单机多实例压测
单台机器启动多个 JMeter Server 实例:
# 实例1
jmeter-server -Dserver.rmi.localport=1099
# 实例2
jmeter-server -Dserver.rmi.localport=1100
Master 配置:
remote_hosts=127.0.0.1:1099,127.0.0.1:1100
17. 性能指标分析与调优
17.1 核心性能指标
17.1.1 响应时间指标
| 指标 | 说明 | 计算方式 |
|---|---|---|
| Average | 平均响应时间 | 所有请求响应时间平均值 |
| Median (P50) | 中位数 | 50% 请求响应时间低于此值 |
| 90th Percentile (P90) | 90分位 | 90% 请求响应时间低于此值 |
| 95th Percentile (P95) | 95分位 | 95% 请求响应时间低于此值 |
| 99th Percentile (P99) | 99分位 | 99% 请求响应时间低于此值 |
| Min/Max | 最小/最大响应时间 | - |
💡 性能评估优先级:P99 > P95 > P90 > Median > Average。平均值容易被极端值扭曲。
17.1.2 吞吐量指标
| 指标 | 说明 |
|---|---|
| TPS (Transactions Per Second) | 每秒事务数,业务级指标 |
| QPS (Queries Per Second) | 每秒查询数,接口级指标 |
| RPS (Requests Per Second) | 每秒请求数 |
| Hits Per Second | 每秒点击数 |
TPS 与 QPS 区别:TPS 关注事务(一笔完整业务),QPS 关注单次请求。一次事务可能包含多次请求。
17.1.3 资源指标
| 指标 | 警戒值 | 危险值 |
|---|---|---|
| CPU 使用率 | > 70% | > 85% |
| 内存使用率 | > 70% | > 85% |
| 磁盘 I/O | - | - |
| 网络带宽 | > 70% | > 85% |
| 数据库连接数 | > 80% 连接池 | 连接池耗尽 |
17.2 Little’s Law(利特尔法则)
性能测试的核心理论之一,描述并发数、吞吐量和响应时间的关系:
L = λ × W
L = 并发用户数 (Concurrent Users)
λ = 吞吐量 (Throughput, 请求/秒)
W = 平均响应时间 (Response Time, 秒)
应用示例:
已知: 系统需要支持 500 TPS,平均响应时间 0.2秒
求: 需要多少并发用户?
L = 500 × 0.2 = 100 并发用户
💡 利特尔法则帮助我们在设计测试场景时,根据目标 TPS 和可接受的响应时间,反推需要的并发线程数。
17.3 性能瓶颈分析方法
17.3.1 分层分析法
客户端 (JMeter)
↓ TPS低 / 响应慢
网络层 → ping延迟、带宽、丢包
↓
应用层 → CPU、内存、GC、线程状态
↓
中间件 → 连接池、缓存命中率、队列堆积
↓
数据库 → 慢SQL、锁等待、连接数
↓
系统层 → CPU、内存、磁盘IO、网络IO
17.3.2 常见瓶颈与排查
| 瓶颈现象 | 可能原因 | 排查工具 |
|---|---|---|
| TPS 上不去,响应时间正常 | 线程数不足、连接池太小 | 应用日志、线程dump |
| TPS 上不去,响应时间飙升 | 数据库慢查询、锁竞争 | 数据库监控、慢查询日志 |
| 错误率随时间增长 | 内存泄漏、连接泄漏 | JVM监控、GC日志 |
| TPS 波动大 | GC停顿、资源争抢 | GC日志、系统监控 |
| 响应时间波动大 | 网络抖动、缓存穿透 | 网络监控、缓存监控 |
17.3.3 监控工具推荐
JVM 监控:
# jstat 查看 GC 情况
jstat -gcutil <pid> 1000 # 每秒打印一次
# jmap 查看内存
jmap -heap <pid>
jmap -histo <pid> | head -20
# jstack 查看线程
jstack <pid> | grep -A 5 "BLOCKED"
# Arthas(阿里巴巴诊断工具)
./as.sh <pid>
arthas> dashboard # 实时面板
arthas> thread -n 3 # 最忙的3个线程
arthas> trace com.example.Service method # 方法调用链
系统监控:
# CPU/内存综合
top -p <pid>
htop
# 磁盘 IO
iostat -x 1
# 网络
iftop
nethogs
# 综合监控(推荐)
# Prometheus + Grafana + node_exporter
数据库监控:
-- MySQL 查看当前连接
SHOW STATUS LIKE 'Threads%';
-- 查看慢查询
SHOW VARIABLES LIKE 'slow_query%';
-- 查看锁等待
SELECT * FROM information_schema.INNODB_LOCK_WAITS;
-- 查看正在执行的 SQL
SHOW FULL PROCESSLIST;
17.4 JMeter 自身调优
当 JMeter 自身成为瓶颈时(单机无法模拟足够并发):
# jmeter.bat / jmeter.sh 中修改 JVM 参数
# 堆内存调大(根据机器内存调整)
HEAP="-Xms2g -Xmx4g"
# 元空间
GC_ALGO="-XX:MaxMetaspaceSize=512m"
# 推荐 JVM 配置(16GB 机器)
HEAP="-Xms4g -Xmx8g -XX:MaxMetaspaceSize=512m"
其他优化项:
# jmeter.properties
# 减少 JMeter 自身日志
jmeter.save.saveservice.response_data=false
jmeter.save.saveservice.response_data.on_error=false
jmeter.save.saveservice.samplerData=false
# 使用 Backend Listener 替代本地文件记录
# 减少 Results File 写入
# 禁用 GUI 组件(非GUI模式自动禁用)
18. JMeter 插件
18.1 插件管理器安装
- 下载 JMeter Plugins Manager 的 jar 包
- 放入
$JMETER_HOME/lib/ext/目录 - 重启 JMeter
- 菜单 Options → Plugins Manager
18.2 必装插件推荐
18.2.1 Custom Thread Groups
提供高级线程组,实现阶梯加压:
Ultimate Thread Group:
| Start Threads Count | Initial Delay | Ramp Up Time | Hold Time | Shutdown Time |
|---|---|---|---|---|
| 100 | 0 | 60 | 300 | 10 |
| 100 | 60 | 60 | 240 | 10 |
| 100 | 120 | 60 | 180 | 10 |
效果:0秒启动100用户,60秒后再加100,120秒后再加100,逐步加压到300用户。
Stepping Thread Group:
可视化阶梯加压,适合压力测试场景。
18.2.2 3 Basic Graphs
提供更多图表:
- Response Times Over Time
- Active Threads Over Time
- Bytes Throughput Over Time
- Hits Per Second
- Response Codes Per Second
18.2.3 PerfMon Plugin
监控服务器资源指标(CPU、内存、磁盘、网络):
- 在被测服务器上启动 ServerAgent
./startAgent.sh --interval 1
- JMeter 中添加
jp@gc - PerfMon Metrics Collector
Host: 192.168.1.100
Port: 4444
Metric to collect: CPU, Memory, Network I/O
18.2.4 Dummy Sampler
模拟请求(不发送真实请求),用于调试脚本逻辑:
Response Code: 200
Response Data: {"code":200,"data":{"token":"test_token"}}
💡 调试关联脚本时,用 Dummy Sampler 模拟前一个请求的响应,快速验证提取器配置是否正确。
18.2.5 Throughput Shaping Timer
精确控制吞吐量的定时器:
Start Threads: 100
End Threads: 500
Shaping Period: 60 (60秒内从100加到500)
18.3 常用插件一览
| 插件 | 功能 |
|---|---|
| Custom Thread Groups | 阶梯加压线程组 |
| 3 Basic Graphs | 基础图表扩展 |
| 5 Additional Graphs | 高级图表 |
| PerfMon | 服务器资源监控 |
| Dummy Sampler | 模拟采样器 |
| Throughput Shaping Timer | 吞吐量塑形定时器 |
| JSON Plugins | JSON 相关工具 |
| WebSocket Samplers | WebSocket 协议测试 |
| Kafka Samplers | Kafka 测试 |
| Dubbo Sampler | Dubbo 接口测试 |
19. CI/CD 集成(Jenkins)
19.1 JMeter + Jenkins 自动化性能测试
将性能测试纳入 CI/CD 流程,实现每次代码提交自动执行性能回归。
19.2 安装 Performance Plugin
- Jenkins → Manage Jenkins → Plugins → Available
- 搜索
Performance并安装 - 重启 Jenkins
19.3 Jenkins Pipeline 配置
pipeline {
agent any
environment {
JMETER_HOME = '/usr/local/apache-jmeter-5.6.3'
TEST_PLAN = 'product_test.jmx'
}
stages {
stage('Checkout') {
steps {
git 'https://github.com/your-repo/performance-tests.git'
}
}
stage('Run JMeter') {
steps {
sh """
${JMETER_HOME}/bin/jmeter -n -t ${TEST_PLAN} \
-l result.jtl \
-e -o ./report/ \
-Jthreads=100 \
-JrampUp=60 \
-Jduration=300
"""
}
}
stage('Publish Report') {
steps {
// Performance Plugin 解析结果
perfReport modeThroughput: true,
modeResponseTime: true,
errorFailedThreshold: 1,
errorUnstableThreshold: 0.5,
compareBuildPrevious: true,
configType: 'PRT',
sourceDataFiles: 'result.jtl'
// 发布 HTML 报告
publishHTML(target: [
allowMissing: false,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: 'report',
reportFiles: 'index.html',
reportName: 'JMeter Report'
])
}
}
stage('Performance Gate') {
steps {
script {
// 解析结果并判断是否通过性能门禁
def result = sh(returnStdout: true,
script: """
awk -F',' 'NR>1 {
if (\$4 > 500) {print "FAIL"; exit}
}' result.jtl
""").trim()
if (result == 'FAIL') {
error("性能测试未通过: P95 响应时间超过 500ms")
}
}
}
}
}
post {
always {
archiveArtifacts artifacts: 'result.jtl,report/**', allowEmptyArchive: true
}
}
}
19.4 性能门禁策略
| 指标 | 门禁规则 | 级别 |
|---|---|---|
| 错误率 | > 1% | 失败 |
| 错误率 | > 0.5% | 不稳定 |
| P95 响应时间 | > 500ms | 失败 |
| TPS 下降 | > 10%(对比上次) | 不稳定 |
| APDEX | < 0.7 | 失败 |
19.5 命令行解析结果
#!/bin/bash
# parse_jmeter_result.sh
RESULT_FILE=$1
# 提取关键指标
TOTAL=$(grep -c "," $RESULT_FILE)
# 注意:根据 jtl 格式调整字段索引
echo "Total Samples: $TOTAL"
# 使用 JMeter 自带工具分析(如果有)
# 或使用第三方工具如 jmeter-results-parser
20. 最佳实践与常见问题
20.1 JMeter 最佳实践
20.1.1 脚本编写
-
GUI 编写,非 GUI 执行:永远在 GUI 中编写和调试脚本,在非 GUI 中执行压测
-
使用测试片段(Test Fragment):将可复用的请求逻辑放入 Test Fragment,通过 Module Controller 引用,避免重复
-
合理命名元件:给每个元件取有意义的名字,便于维护
✅ 错误的命名: HTTP Request
✅ 正确的命名: 用户登录-POST /api/login
-
使用变量和属性:将服务器地址、端口等配置定义为变量,便于环境切换
-
添加注释:使用 Test Plan 的 Comments 字段或 Dummy Sampler 添加说明
20.1.2 性能测试执行
-
先做基准测试:单用户运行获取基准数据,作为对比参考
-
逐步加压:不要直接用大并发,先小后大,观察拐点
-
足够的持续时间:短时间测试无法发现内存泄漏等问题,稳定性测试至少 1 小时以上
-
预热阶段:JIT 编译、缓存预热需要时间,前 1~2 分钟的数据可以忽略
-
多次测试取平均:单次测试结果有偶然性,建议同一场景测试 3 次取中位数
-
监控被测系统:压测时同时监控系统资源,否则无法定位瓶颈
20.1.3 资源优化
-
禁用不必要的监听器:压测时删除 View Results Tree、View Results in Table 等监听器
-
减少日志写入:
jmeter.save.saveservice.response_data=false
jmeter.save.saveservice.samplerData=false
jmeter.save.saveservice.encoding=false
-
使用 Backend Listener:大规模测试时用 InfluxDB 收集数据,替代本地 .jtl 文件
-
合理设置 JVM:根据机器内存调整 JMeter 的 JVM 堆大小
-
CSV 文件控制大小:大文件读取影响性能,按需准备数据量
20.2 常见问题排查
Q1: 压测时大量 Connection Refused
原因:服务器连接数达到上限,或 JMeter 端口耗尽
解决:
# 1. 检查服务器最大连接数
ulimit -n # 默认 256/1024,调大
ulimit -n 65535
# 2. JMeter 端 (客户端) 调整
# macOS/Linux 释放端口更快
sudo sysctl -w net.ipv4.tcp_tw_reuse=1
# 扩大本地端口范围
sudo sysctl -w net.ipv4.ip_local_port_range="1024 65535"
Q2: OutOfMemoryError: Java heap space
原因:JMeter 内存不足
解决:
# 增大堆内存
HEAP="-Xms4g -Xmx8g"
# 或减少并发 / 使用分布式
Q3: HTTPS 证书错误
解决:
# 在 HTTP Request 中配置
# 或在 jmeter.properties 中
https.use.cached.ssl.context=false
https.socket.https.cps=0
Q4: 响应乱码
解决:
# 在 HTTP Request 中设置
Content Encoding: UTF-8
# 或在后置处理器中
prev.setDataEncoding("UTF-8")
# jmeter.properties
sampleresult.default.encoding=UTF-8
Q5: TPS 始终上不去
排查思路:
- 检查线程数和 Ramp-Up 设置是否合理
- 检查是否有定时器限制了请求频率
- 检查 JMeter 机器 CPU/内存是否打满
- 检查被测系统是否有限流(如 Sentinel、Nginx limit_req)
- 检查数据库连接池是否已满
- 检查网络带宽是否成为瓶颈
Q6: 分布式测试 Slave 连接失败
解决:
# 1. 检查防火墙
# 2. 检查 RMI 端口
# 3. 使用 -Djava.rmi.server.hostname 指定正确 IP
jmeter-server -Djava.rmi.server.hostname=192.168.1.10
# 4. 确保 SSL 配置一致
20.3 JMeter 快捷键
| 快捷键 | 功能 |
|---|---|
Ctrl+S | 保存测试计划 |
Ctrl+E | 清除所有结果 |
Ctrl+R | 运行测试 |
Ctrl+. | 停止测试 |
Ctrl+, | 关闭测试 |
Ctrl+Shift+E | 清除所有结果(全部) |
Ctrl+0 | 函数助手对话框 |
20.4 学习路径建议
入门阶段
├── 理解性能测试基本概念
├── 安装 JMeter,跑通第一个测试
├── 熟悉 GUI 界面和核心组件
└── 学会查看结果树和聚合报告
↓
进阶阶段
├── 掌握参数化(CSV Data Set Config)
├── 掌握关联(JSON/正则提取器)
├── 掌握断言
├── 掌握逻辑控制器(事务、循环、条件)
└── 学会使用非 GUI 模式
↓
高级阶段
├── 分布式压测
├── JSR223 Groovy 脚本编程
├── 性能瓶颈分析与调优
├── 插件开发
└── CI/CD 集成
附录
A. JMeter 内置函数速查
| 函数 | 说明 | 示例 |
|---|---|---|
${__time(,)} | 毫秒时间戳 | 1718769600000 |
${__time(yyyy-MM-dd,)} | 格式化日期 | 2025-06-19 |
${__Random(1,100,)} | 随机整数 | 42 |
${__RandomString(10,abcdefg,)} | 随机字符串 | cdfbageba |
${__UUID()} | UUID | a3b4c5d6-… |
${__counter(TRUE,)} | 线程计数器 | 1, 2, 3… |
${__threadNum} | 线程编号 | 1 |
${__P(propName)} | 读取属性 | - |
${__property(propName)} | 读取属性 | - |
${__setProperty(name,value)} | 设置属性 | - |
${__V(var)} | 嵌套变量 | - |
${__split(str,var,sep)} | 分割字符串 | - |
${__groovy(expr,)} | 执行Groovy | - |
${__jexl3(expr,)} | 执行JEXL | - |
${__log(msg)} | 写日志 | - |
${__logn(msg)} | 写日志(换行) | - |
${__BeanShell(script)} | BeanShell脚本 | - |
${__evalVar(var)} | 求值变量 | - |
${__FileToString(path)} | 读取文件内容 | - |
${__base64Encode(str)} | Base64编码 | - |
${__base64Decode(str)} | Base64解码 | - |
${__urlencode(str)} | URL编码 | - |
${__urldecode(str)} | URL解码 | - |
${__md5(str)} | MD5哈希 | - |
${__sha256(str)} | SHA256哈希 | - |
B. JSR223 内置变量速查
| 变量 | 类型 | 说明 |
|---|---|---|
vars | JMeterVariables | 当前线程的变量,vars.get()/vars.put() |
props | Properties | 全局属性,props.get()/props.put() |
prev | SampleResult | 上一个采样器结果 |
ctx | JMeterContext | 当前线程上下文 |
log | Logger | 日志对象,log.info()/log.error() |
OUT | PrintStream | System.out |
AssertionResult | AssertionResult | 断言结果(仅断言中可用) |
ResponseCode | String | 响应码 |
ResponseMessage | String | 响应消息 |
SampleLabel | String | 采样器名称 |
SamplerData | String | 请求数据 |
ctx.getPreviousResult() | SampleResult | 上一个采样器结果 |
C. 推荐学习资源
结语:性能测试不是"跑个工具看个数字"那么简单。优秀的性能测试工程师需要理解系统架构、掌握测试方法论、熟悉监控分析工具。JMeter 是强大的武器,但真正决定测试质量的是你的分析能力和对系统的理解深度。希望本文能帮助你从入门走向精通,在实践中不断积累经验。

12万+

被折叠的 条评论
为什么被折叠?



