Skip to content

JMeter 参数化与关联 ​

T2-013必学进阶约 60 分钟JDK 21测试专项 B:接口·UI·性能 · 性能测试

1. 本节定位 ​

《JMeter 断言与指标解读》讲指标解读;本节讲**"让压测数据更真实"**的两件事:

text
参数化(Parameterization):让每个虚拟用户用不同的数据      ← 请求"不一样" ✓
关联(Correlation):      把上一个响应里的值,传给下一个请求  ← 请求"有前后关系" ✓

如果这两件事都不做,压测就是"同一个人用同一份数据、以机器速度反复点同一个按钮" ✗ ——那测出来的东西,离真实很远。

2. 学习目标 ​

  • 会用 CSV 数据文件 + 随机函数做参数化 ★★;
  • 会用提取器做关联(JSON / 正则)★★;
  • 说出"参数固定"为什么会扭曲压测结果 ★★★(含缓存的影响);
  • 知道参数化的数据量与唯一性要求(CSV 行数 vs 迭代次数)★;
  • 说出"关联"与"参数化"的适用场景区别 ✓。

3. 前置知识检查 ​

4. 为什么需要这个知识 ​

先看一个"固定参数压测"的陷阱:

text
压测脚本:GET /api/products?keyword=手机        ← ★ 每次都一样
结果:QPS 3000,P95 = 8ms                        ← 看起来很猛

真实用户:搜"手机"、"耳机"、"键盘"、"充电器"……    ← 千差万别
真实结果:数据库要处理各种查询、缓存命中率低得多    ← 慢得多 ✗✗

问题出在哪:

层固定参数的影响
应用缓存同一个 key 反复命中缓存 → 几乎不查库 ✗✗
数据库缓存同一个执行计划、同一批数据页反复命中 ✗
操作系统缓存同一份文件反复读 ✗
索引同一个索引项反复命中,看不出"多个索引项竞争"的代价 ⚠️

你压的是"缓存",不是"系统" ✗✗ ——这是压测里最隐蔽的高估 ✓✓

5. 核心概念 ​

5.1 参数化 ★★ ​

目标:让每个虚拟用户、每次迭代,用"不一样的数据" ✓

JMeter 的三种做法:

做法适用例子
CSV Data Set Config ★★一批真实形态的数据(如用户、商品 ID)users.csv,每行一个用户名
随机函数(__Random / __RandomString)★数值 / 字符串productId = P-${__Random(1,10000)}
计数器(Counter)顺序递增orderNo = ORD-${__counter(FALSE)}

★ CSV 数据集的四个关键配置:

配置说明陷阱
Filename文件路径相对 JMeter 启动目录(不是脚本目录)⚠️
Variable Names列名与请求里的 ${name} 对应 ✓
Recycle on EOF读完是否循环压测通常设为 true ✓
Stop thread on EOF读完是否停线程一般 false ✓

★ "数据量"要求:

text
CSV 的行数 应当 ≥ 并发数 × 期望的"数据多样性"

如果 CSV 只有 10 行而并发 100:10 个数据被 100 个线程反复复用 → 缓存又命中了 ✗

一个经验值:至少准备"并发的 10 倍"行数据,让同一个数据在压测期间很少被重复用到 ✓

5.2 关联 ★★★ ​

目标:把上一个响应里的值提取出来,传给下一个请求 ✓

典型场景(这就是"真实的用户旅程"):

text
① 登录            → 响应里有 token
② 用 token 查订单  → 响应里有 orderNo
③ 用 orderNo 查详情
④ 用 orderNo 取消订单

JMeter 的提取器:

提取器适用例子
JSON JMESPath Extractor ★★JSON 响应(现代首选)$.data.token
Regular Expression Extractor文本 / HTML"token":"(.+?)"
Boundary Extractor左右边界明确的文本比正则简单 ✓
XPath ExtractorHTML / XML—

提取出来的值怎么用:放在变量里,后续请求用 ${变量名} 引用 ✓

★ 关联 vs 参数化的区别(容易混):

参数化关联
数据从哪来外部文件 / 函数(事前准备)上一个响应(运行中产生)★
解决什么"让数据不同""让请求有前后关系" ★
典型CSV 里的用户名token、orderNo、sessionId

一句话:

参数化是"我准备好数据";关联是"我从系统里拿数据"。 ✓

5.3 混合场景:真实的负载长什么样 ★★ ​

真实的系统负载不是一个接口,而是一组接口按某种比例混合 ✓

例子(电商):

text
浏览商品   80%
搜索       12%
加购        5%
下单        2.5%
支付        0.5%

★ 为什么必须混合压测:

原因说明
不同接口争不同资源 ★读走缓存、写走 DB 与事务 ——只压读接口会严重高估写能力 ✗✗
接口之间有依赖下单依赖商品存在(且要"真实存在")★
连接池的竞争写接口占满 DB 连接池 → 读接口也跟着慢 ✗
更接近真实的用户旅程用户不是"只调一个接口" ✓

JMeter 的实现方式:

方式做法
多个线程组每个接口一个线程组,按比例设线程数 ✓
Throughput Controller ★一个线程组内按百分比分配 ✓
Random Controller随机选一个执行 ⚠️(比例不可控)

5.4 本轮的诚实反思 ★★★ ​

本轮的压测(T2-003 第 7 节)用的是固定参数:

text
http://localhost:18090/raw/hello?name=load&age=1        ← ★ 参数完全固定

所以那次数据的偏差方向是明确的:

影响说明
偏乐观 ⚠️同一个请求被反复执行 → 代码路径、数据都高度重复
但结论仍成立 ✓趋势(拐点、延迟爆炸)不依赖参数变化,因为瓶颈在 CPU 与线程调度
绝对数字仍不可当容量结论 ✗这一点在 T2-003 已经说明(同机压测)✓

★ 这恰好是一个"教学样本":

压测报告必须写清"用了什么数据" ✓ ——否则读者无法判断这个数字是"乐观"还是"保守" ✓✓

如果被测接口有缓存,而压测参数固定 → 结果严重偏乐观 ✗✗

一个实用的自检问题:

text
"如果我把参数换成随机的,结果会差多少?"
   —— 如果差很多 → 上次压的是缓存 ✗
   —— 如果差不多 → 说明瓶颈不在数据层 ✓

6. 递进示例 ​

6.1 最小示例:随机参数 ​

text
HTTP 请求路径:/api/products?keyword=${__RandomString(4,abcdefghij)}

6.2 业务示例:登录 → 下单(关联) ​

text
① HTTP 请求:POST /api/auth/login
   后置处理器 → JSON 提取器:变量名 token,表达式 $.data.token
② HTTP 请求:POST /api/orders
   请求头:Authorization: Bearer ${token}
   后置处理器 → JSON 提取器:变量名 orderNo,表达式 $.orderNo
③ HTTP 请求:GET /api/orders/${orderNo}

★ 这就是"一次真实的用户旅程" ✓ ——它同时做了参数化(不同用户)与关联(token、orderNo) ✓

6.3 反例:所有请求完全一样 ​

见第 4 节 ✓

7. 实测:参数化在代码里长什么样 ​

JMeter 的"提取器"在代码里就是"解析响应 → 取值" ✓

对照本项目的实现(T2-005 的 full_lifecycle):

java
// ① 关联:从创建订单的响应里提取 orderNo
String orderNo = given().contentType(JSON)
    .body("""{"productId":"P-LIFE","quantity":1,"address":"北京市"}""")
.when().post("/api/orders")
.then().statusCode(201)
    .extract().jsonPath().getString("orderNo");       // ★ 等价于 JMeter 的 JSON 提取器

// ② 用提取出的值发下一个请求
given().when().get("/api/orders/" + orderNo)          // ★ 等价于 ${orderNo}
    .then().statusCode(200);

★ 一行 extract() 就是 JMeter 的"提取器" ✓ ——概念完全对应 ✓

参数化在代码里:

java
// ★ 每次用不同数据(等价于 CSV / 随机函数)
String productId = "P-" + UUID.randomUUID().toString().substring(0, 8);

这与 T2-006 的"自建自清 + 唯一数据"是同一个做法 ✓ ——测试的"数据隔离"与压测的"数据多样性"本质同源 ✓✓

★ 反过来说明:

本轮压测的固定参数(name=load&age=1)在真实项目里应该改成随机值 ✓

这不需要 JMeter——《JMeter 压力测试建模》 的 LoadTestLesson 里加一行随机参数就行 ✓

(这也是 T2-003 扩展任务的方向) ✓

8. Debug 观察点 ​

  1. 看应用日志的 SQL(《日志框架与日志规范》):参数固定时,SQL 是不是反复相同的几条 ★ ——这就是"压缓存"的现场 ✓;
  2. 把参数改成随机后再压一次:对比 QPS 与 P95 的差异 ★★ ——差异就是"缓存带来的高估" ✓;
  3. 故意让 CSV 只有 5 行、并发 50:观察结果偏向乐观(数据被反复复用)⚠️;
  4. 关联失败会怎样:提取器取不到值 → 变量是空 → 后续请求全 404/400 ✗ ——错误率飙升 ✓(所以关联后要看错误率)✓。

9. 常见错误 ​

#错误说明
1压测参数固定 ✗✗压的是缓存(第 4 节)✗✗
2CSV 数据量太小 ⚠️数据被反复复用,结果偏乐观 ✗
3CSV 用了相对路径但没确认工作目录读不到文件 → 变量为空 → 请求全错 ✗✗
4关联没用"提取器",而是手写死 token ✗token 会过期 → 压一会儿全 401 ✗✗
5只压一个接口 ✗✗严重高估系统整体能力(5.3)✗
6不检查"关联是否成功"提取失败会静默变成空值 ⚠️

第 4 条特别真实:手工复制一个 token 到脚本里,跑几分钟后过期了 → 后面全是 401,而报告显示"错误率 80%" ✗ ——正确做法是每次迭代都重新登录(或用"仅一次控制器") ✓

10. 动手实验 ​

目标:亲手体会"参数固定"与"参数随机"的差距。

步骤:

  1. 改 LoadTestLesson 的 URL,加一个随机参数(如 &q=${随机}),跑一次 ✓;
  2. 对比两次结果(固定 vs 随机)—— 差异就是"数据重复带来的高估" ★★;
  3. 在本项目里造一个"有缓存"的接口(如用 ConcurrentHashMap 缓存查询结果),用固定参数压 → 极快 ✗;再用随机参数压 → 慢得多 ✓ ——亲眼看到"压的是缓存" ★★★;
  4. 做一个"关联"的压测:先建订单拿 orderNo,再查订单(用两段请求串起来)✓;
  5. 用 JMeter 配一遍(如果装了):CSV + JSON 提取器 + 混合线程组 ✓。

验收方式:能说出第 3 步"为什么固定参数极快、随机参数慢"。

扩展任务:设计一个"混合场景"压测:

  • 比例:查询 80% / 创建 20%;
  • 说明:为什么"创建的 20%"可能比"查询的 80%"更早成为瓶颈(提示:写操作有事务与锁)★。

11. 练习题 ​

  1. 判断:参数化与关联是同一个概念。(对 / 错)
  2. 填空:让数据"不一样"用 ______;让请求"有前后关系"用 ______。
  3. 简答:为什么"固定参数的压测结果偏乐观"?
  4. (代码阅读)
text
线程数:100
CSV 行数:5
迭代次数:每线程 20 次

有什么问题? 5. (编程)给 T2-005 的 full_lifecycle 加上"随机 productId",并说明它为什么更接近真实。

参考答案(要点)
  1. 错。参数化 = "我准备数据";关联 = "我从系统里拿数据" ✓(5.2)
  2. 参数化;关联 ✓
  3. 因为请求高度重复 → 各级缓存全部命中(应用缓存、DB 缓存、OS 缓存)✗ ——你压的是缓存,不是系统 ✓★
  4. 5 行数据被 100 个线程 × 20 次迭代反复复用 ✗ ——数据多样性极低,几乎等于"参数固定" ✓ ——建议至少准备几百行 ✓
  5. 要点:String productId = "P-" + UUID.randomUUID()... ✓ ——理由:真实用户不会用同一个商品下单;随机数据能避免"同一条记录被反复读写",也能覆盖到更多的索引分支与缓存未命中路径 ✓

12. 掌握标准 ​

  • [ ] 会用 CSV 与随机函数做参数化 ★★;
  • [ ] 会用 JSON 提取器做关联,并知道关联与参数化的区别 ★★★;
  • [ ] 知道"固定参数会压到缓存,导致结果偏乐观" ★★★;
  • [ ] 知道 CSV 数据量的要求(并发 × 10 起步)★;
  • [ ] 知道真实的负载是"多接口混合",且不能只压一个接口 ★★;
  • [ ] 会在压测报告里写明"用了什么数据" ✓。

13. 扩展资源 ​

14. 下一步学习 ​

《基线测试、负载测试与压力测试》:三种测试的目标、停止条件与产出。

后续知识

暂无

进度只保存在本机浏览器,可随时清除

个人学习项目:AI 生成内容均经人工复核,第三方资源仅作索引