4. k6 内置参数化方法:__VU 与 __ITER 内置变量
在容量压测中,模拟多用户并发执行多次迭代是核心需求。k6 提供了两个内置变量——__VU 和 __ITER,无需额外安装或配置即可在脚本中直接使用。它们是最轻量级的参数化手段,适用于循环控制、用户身份区分以及日志追踪场景。
4.1 内置变量概述
| 变量名 | 类型 | 含义 | 取值范围 |
|---|---|---|---|
__VU |
整数 | 当前执行该迭代的虚拟用户编号 | 1 ~ options.scenarios[*].vus(或 options.vus) |
__ITER |
整数 | 当前虚拟用户已执行的迭代次数(从 0 开始) | 0 ~ (iterations - 1) 或持续时间内递增 |
关键区别:
__VU 在同一个 VU 的生命周期内保持不变;__ITER 在每个 VU 内部独立计数,不同 VU 的 __ITER 值互不影响。
4.2 基础用法:打印用户与迭代编号
以下脚本展示了如何在 default 函数中输出 __VU 和 __ITER,验证其取值规律:
import http from 'k6/http';
import { sleep } from 'k6';
export const options = {
vus: 3,
iterations: 6,
};
export default function () {
console.log(`VU: ${__VU}, ITER: ${__ITER}`);
http.get('https://test.k6.io');
sleep(1);
}
执行结果分析(部分输出):
INFO[0000] VU: 1, ITER: 0
INFO[0000] VU: 2, ITER: 0
INFO[0000] VU: 3, ITER: 0
INFO[0001] VU: 1, ITER: 1
INFO[0001] VU: 2, ITER: 1
INFO[0001] VU: 3, ITER: 1
可以看到:3 个 VU 并行执行,每个 VU 各自从 ITER=0 开始计数,总共 6 次迭代(每个 VU 执行 2 次)。
4.3 基于 __VU 实现用户身份轮询
在容量压测中,不同 VU 通常需要模拟不同用户。利用 __VU 作为数组索引,可以轻松实现用户身份轮询:
const users = [
{ username: 'user_a', : '_a' },
{ username: 'user_b', : '_b' },
{ username: 'user_c', : '_c' },
];
export default function () {
// 使用 __VU - 1 作为索引(__VU 从 1 开始)
const currentUser = users[__VU - 1];
console.log(`VU ${__VU} 使用用户: ${currentUser.username}`);
const payload = JSON.stringify({
username: currentUser.username,
: currentUser.,
});
http.post('https://api.example.com/login', payload, {
headers: { 'Content-Type': 'application/json' },
});
sleep(1);
}
数组越界风险:当
options.vus 大于用户数组长度时,__VU - 1 会超出数组范围,导致 undefined 错误。建议使用取模运算 users[(__VU - 1) % users.length] 来安全轮询。
4.4 基于 __ITER 实现迭代内循环控制
__ITER 常用于在同一个 VU 的多次迭代中切换行为。例如,每 3 次迭代执行一次特殊操作:
export default function () {
// 每 3 次迭代执行一次清理操作
if (__ITER % 3 === 0) {
console.log(`VU ${__VU} 执行清理操作,当前 ITER: ${__ITER}`);
http.post('https://api.example.com/cleanup');
}
http.get('https://api.example.com/data');
sleep(1);
}
4.5 组合使用:用户 + 迭代双维度参数化
将 __VU 和 __ITER 组合,可以实现二维参数化,适用于需要同时区分用户身份和操作步骤的场景:
const searchKeywords = ['手机', '电脑', '耳机', '键盘', '鼠标'];
export default function () {
// 用户维度:不同 VU 使用不同用户
const userId = `user_${__VU}`;
// 迭代维度:同一用户每次迭代搜索不同关键词
const keywordIndex = __ITER % searchKeywords.length;
const keyword = searchKeywords[keywordIndex];
console.log(`VU ${__VU} (${userId}) 第 ${__ITER} 次搜索: ${keyword}`);
http.get(`https://api.example.com/search?q=${keyword}&uid=${userId}`);
sleep(1);
}
4.6 内置变量与场景配置的配合
当使用 scenarios 配置多场景时,__VU 和 __ITER 在每个场景内独立计数:
export const options = {
scenarios: {
smoke: {
executor: 'per-vu-iterations',
vus: 2,
iterations: 3,
exec: 'smokeTest',
},
load: {
executor: 'ramping-vus',
startVUs: 1,
stages: [
{ duration: '10s', target: 5 },
{ duration: '20s', target: 5 },
],
exec: 'loadTest',
},
},
};
export function smokeTest() {
console.log(`[smoke] VU: ${__VU}, ITER: ${__ITER}`);
http.get('https://test.k6.io');
}
export function loadTest() {
console.log(`[load] VU: ${__VU}, ITER: ${__ITER}`);
http.get('https://test.k6.io');
sleep(1);
}
两个场景的 __VU 和 __ITER 互不干扰,各自从 1 和 0 开始计数。
4.7 内置变量工作流程
下图展示了 k6 执行引擎如何为每个 VU 分配 __VU 编号,并在每次迭代中递增 __ITER:
4.8 常见陷阱与最佳实践
- 陷阱 1:
__VU从 1 开始,不是 0。用作数组索引时需减 1。 - 陷阱 2:在
setup()和teardown()函数中,__VU和__ITER不可用,因为这两个函数不在 VU 上下文中执行。 - 陷阱 3:使用
shared-iterations执行器时,__ITER在所有 VU 间共享递增,不再是每个 VU 独立计数。 - 最佳实践:在日志输出中始终包含
__VU和__ITER,便于问题定位和结果分析。 - 最佳实践:当 VU 数量超过参数数据源长度时,使用取模运算
%实现循环复用。