跳转至

JMeter 参数化:CSV 与函数助手

参数化做错,压测数字基本作废:所有人用同一个账号 → 缓存命中虚高;所有订单金额相同 → 数据库命中同一索引页;所有手机号重复 → 唯一索引报错,错误率飙升。这篇按"配置项真实语义 → 函数用法 → 分布式分片 → 报错处理"写。

一、参数化的四个层次

层次 来源 典型用途
静态 用户定义变量 环境域名、固定租户
文件 CSV Data Set Config 账号池、商品池、身份证
动态 函数助手 / Groovy 时间戳、UUID、随机数、签名
关联 JSON/正则/XPath 提取器 上一步返回的 token、订单号

真实压测四层混用:账号来自 CSV,请求号用函数生成,token 用提取器关联。

二、CSV Data Set Config 逐项语义

Thread Group
└── CSV Data Set Config
    Filename: data/users.csv
    File encoding: UTF-8
    Ignore first line (only if variable names are not defined): false
    Variable Names: username,password,phone
    Delimiter: ,
    Allow quoted data: true
    Recycle on EOF: true
    Stop thread on EOF: false
    Sharing mode: All threads
└── HTTP Request  → ${username} / ${password}

2.1 Filename:相对路径到底相对谁

JMeter 的相对路径相对于启动目录(通常是 JMETER_HOME/bin),不是 JMX 文件所在目录。所以本地 GUI 能跑、命令行跑就报:

java.io.FileNotFoundException: data/users.csv (No such file or directory)

两种稳定写法:

Filename: ${__P(user.dir)}/data/users.csv     # 启动目录为项目根
Filename: ${__P(data.dir,.)}/users.csv        # 显式传入
jmeter -n -t plan.jmx -Juser.dir=/opt/perf -Jdata.dir=/opt/perf/data

2.2 Variable Names 与 Ignore first line

  • 填了 Variable Names:文件第一行就是数据,不能再勾 Ignore first line(勾了会少一组数据)。
  • 没填 Variable Names:文件第一行被当作列名,此时 Ignore first line 的语义容易混淆——最稳妥是显式填列名,让数据从第一行开始。

2.3 Delimiter 与 Allow quoted data

  • 分隔符写 \t 表示制表符,写 , 是逗号。
  • 字段内含逗号时,必须用双引号包裹,并开启 Allow quoted data:
username,password,address
qa_001,Passw0rd!,"北京市海淀区,中关村大街1号"
  • 字段内含双引号时,用两个双引号转义:"他说""你好"""。
  • 关掉 Allow quoted data 后,引号会被当成普通字符,列会错位。

2.4 Recycle / Stop thread 的真实组合

Recycle on EOF Stop thread on EOF 实际行为 风险
true false 读完回到第一行 账号被重复使用,缓存命中虚高
false true 读完停止该线程 实际并发低于设定值,曲线后段塌陷
false false 继续运行,变量被置为 <EOF> 请求带着 <EOF> 发出去,产生大量假错误

第三种最坑:压测看起来在跑,但后半段请求体全是 <EOF>。

判断数据量是否够:

需要的行数 ≥ 线程数 × 循环次数

不够就补数据,不要靠 Recycle 硬撑。

2.5 共享模式:最容易理解错的一项

模式 文件实例 行为 适用
All threads 全局 1 份 所有线程共用游标,每行只被取一次 全局唯一账号池
Current thread group 每个线程组 1 份 组内共享,组间各从第一行开始 多业务组各自独立数据
Current thread 每个线程 1 份 每个线程都从第一行读,值会重复 需要每线程固定账号(如长连接)

选错的后果:

  • 想要"每人一个唯一账号",却选了 Current thread → 每个线程都从第 1 行开始,账号全重复。
  • 想要"每个线程固定一个账号",却选了 All threads → 每次迭代换账号,登录态无法复用。

有状态业务(登录后一直用同一账号)用 Current thread + 每人一行;无状态业务(每次请求独立)用 All threads 摊开账号池。

三、函数助手:语法与坑

JMeter 函数在每次使用时求值,不是启动时求值。

函数 语法 说明
计数器 ${__counter(FALSE,)} 第一参数 TRUE=每线程独立,FALSE=全局共享;第二参数为变量名,可空
线程号 ${__threadNum} 从 1 开始
UUID ${__UUID} 每次调用生成新 UUID
时间 ${__time(yyyyMMddHHmmss,)} 第二参数可存变量
时间偏移 ${__timeShift(yyyy-MM-dd,,P1D,,)} 第四参数为输出变量
随机整数 ${__Random(1,999999,)} 含边界
随机串 ${__RandomString(8,abcdef0123456789,)} 第二参数是字符集
嵌套变量 ${__V(user_${__counter(FALSE,)})} 用变量名拼变量名
属性 ${__P(env,test)} 读 JVM 属性,分布式可传
Groovy ${__groovy(1+1,)} 有编译开销,别在高频请求里用

3.1 唯一手机号的正确拼法

${__time(yyMMddHHmmss,)}${__threadNum}${__counter(FALSE,)}
  • __time 提供运行批次前缀,避免多次压测之间撞号;
  • __threadNum 保证线程内不同;
  • __counter(FALSE,) 全局自增,保证迭代内不同。

反例:

${__Random(10000000000,19999999999,)}   # 1 万并发下必然碰撞
${__time(,)}${__threadNum}              # 同一毫秒内同线程重复

3.2 __counter 的第一参数

${__counter(TRUE,)}    # 每个线程各自从 1 开始:t1:1,2,3  t2:1,2,3
${__counter(FALSE,)}   # 全局共享:1,2,3,4,5...

分布式压测时,__counter 是每个 worker 进程独立的,所以 w1 和 w2 都会生成 1、2、3。要全局唯一,必须拼上机器标识:

${__machineName()}_${__counter(FALSE,)}

3.3 __V 的用途

从 CSV 里读出一批变量 user_1、user_2…,按计数器轮询:

${__V(user_${__counter(FALSE,)})}

注意计数器超过变量数量时取到空值,需要配合 __jexl3 取模:

${__V(user_${__jexl3(${__counter(FALSE,)} % 50 + 1,)})}

四、分布式压测的数据分片

这是分布式参数化最容易出错的地方:CSV 文件要在每台 worker 的同一路径存在,且各机数据不能重复。

4.1 方案 A:按机器名分文件(推荐)

Filename: ${__P(data.dir)}/users_${__machineName()}.csv

准备:

data/users_perf-w1.csv
data/users_perf-w2.csv
data/users_perf-w3.csv

每台 worker 只读自己的文件,天然不重复。

4.2 方案 B:按机器 IP 分文件

Filename: ${__P(data.dir)}/users_${__machineIP()}.csv

IP 里的点可以直接做文件名,也可以替换成下划线(用 __groovy 或 __strReplace 处理)。

4.3 方案 C:一份文件 + 分摊偏移

不推荐,容易算错。若必须做,用 __counter 的全局值 + worker 偏移:

行号 = workerIndex * rowsPerWorker + localCounter

JMeter 没有内置 workerIndex,只能靠 __machineName 映射,复杂度高于方案 A。

4.4 文件同步

# 用 rsync 保证每台 worker 的文件一致
for host in w1 w2 w3; do
  rsync -av data/users_${host}.csv perf@${host}:/opt/perf/data/
done

注意:worker 上跑的是 jmeter-server,路径解析同样相对启动目录。要统一下发路径,用 -G(会发送到所有远程 worker),而不是 -J(只在 Controller 本地生效):

jmeter -n -t plan.jmx -r -Gdata.dir=/opt/perf/data

或者把 data.dir=/opt/perf/data 直接写进每台 worker 的 bin/user.properties,不依赖命令行。

五、大数据量:别把 CSV 全读进内存

CSV Data Set Config 会按需读行,不会一次载入全部,但文件特别大(百万行以上)时仍建议:

  • 用 __StringFromFile 逐行读取(每个线程独立文件句柄):
${__StringFromFile(${__P(data.dir)}/phones.txt,,,,)}
  • 或 __CSVRead 手动控制列:
${__CSVRead(${__P(data.dir)}/users.csv,0)}   # 第 0 列
${__CSVRead(${__P(data.dir)}/users.csv,1)}   # 第 1 列
${__CSVRead(${__P(data.dir)}/users.csv,next)} # 切到下一行
  • 生成型数据(手机号、订单号)直接用函数生成,比读文件快。

六、验证参数真的生效

不要只看脚本没报错。按这个顺序验证:

  1. 1 线程 3 循环:查看结果树里请求体的值是否每次变化。
  2. Debug Sampler + View Results Tree:确认变量名与值,避免拼错变量名导致发出字面量 ${username}。
  3. 看服务端:日志里的用户 ID、订单号是否分散。
  4. 看聚合报告:若账号被复用,登录接口 RT 会异常低(缓存命中)。
  5. 看错误率:<EOF>、重复键、401 都会体现在错误里,先看 _failures.csv 的前几类。

一个快速自检:在请求里加一个断言,校验 ${phone} 长度为 11 且不以 $ 开头。

// JSR223 断言(Groovy)
def phone = vars.get("phone")
assert phone != null && phone ==~ /\d{11}/ : "phone invalid: " + phone

七、真实报错对照表

报错 根因 处理
FileNotFoundException: users.csv 相对路径基于启动目录 用 ${__P(user.dir)} 绝对路径
第一列变量读不到,值为空 CSV 带 UTF-8 BOM 用 sed -i '1s/^\xEF\xBB\xBF//' 去 BOM,或勾 Ignore first line 并显式写变量名
MalformedInputException 文件编码与 File encoding 不一致 统一 UTF-8,或改为 GBK
列错位 字段含逗号未加引号 加引号并开 Allow quoted data
大量 401 Recycle 打开后账号复用,登录态被踢 换 Current thread 或补数据
后半段全是 <EOF> Recycle=false + Stop thread=false 补数据,或打开 Stop thread 并接受并发下降
唯一索引冲突 计数器/随机数碰撞 拼 __time + __threadNum + 全局计数器
分布式下账号重复 各 worker 各自从第一行读 按机器名/IP 分文件
压测机 CPU 高、TPS 上不去 函数在每次请求都求值,__groovy 编译开销大 用 __counter/__time,复杂逻辑放 JSR223 预编译

八、性能开销提示

  • 每个函数调用都要走解释器,高频请求里叠加 5 个函数会明显增加 CPU。
  • __groovy 每次调用都会编译脚本(除非用 JSR223 + 缓存编译),不要在 1 万 TPS 的采样器里用。
  • __UUID 比 __counter 贵,能用计数器的场景别用 UUID。
  • 断言和监听器同样吃 CPU:压测时关掉"查看结果树",用 -n 非 GUI 模式,结果写 JTL。

九、检查清单

  • 账号/商品池行数 ≥ 线程数 × 循环次数
  • CSV 编码 UTF-8、无 BOM、含逗号字段有引号
  • Variable Names 与列一一对应,请求里变量名无拼写错误
  • 共享模式与业务是否有状态匹配
  • Recycle/Stop thread 组合符合预期,不产生 <EOF>
  • 唯一值拼接了时间前缀与线程号
  • 分布式按机器名/IP 分文件,路径用绝对路径
  • 用 Debug Sampler 验证过变量,不只看脚本无报错

相关阅读