3. k6 脚本基础结构:import 模块、export default 函数、options 配置、VU 与 iteration 概念、生命周期钩子
在编写 k6 压测脚本之前,必须理解其核心的脚本结构。k6 脚本基于 JavaScript (ES6+) 语法,但运行在 Go 语言编写的 goja 虚拟机中,因此不支持浏览器 DOM API。一个标准的 k6 脚本由五个核心要素构成:模块导入、全局配置、默认导出函数、虚拟用户与迭代模型以及生命周期钩子。
核心原则: k6 脚本不是传统的“线性执行”脚本,而是围绕 VU (Virtual User) 和 iteration (迭代) 的并发模型设计的。理解这个模型是编写有效压测脚本的前提。
3.1 脚本骨架与 import 模块
每个 k6 脚本都以 import 语句开始,用于引入 k6 内置模块或本地模块。这些模块提供了 HTTP 请求、指标收集、检查点等功能。
// 导入 k6 内置模块
import http from 'k6/http';
import { check, sleep } from 'k6';
import { Rate, Trend } from 'k6/metrics';
// 导入本地辅助模块
import { generatePayload } from './helpers.js';
// 全局配置
export const options = {
vus: 10,
duration: '30s',
};
// 默认导出函数(VU 入口)
export default function () {
// 每个 VU 每次迭代执行的代码
let res = http.get('https://test.k6.io');
check(res, {
'status is 200': (r) => r.status === 200,
});
sleep(1);
}
| 模块路径 | 常用导出 | 用途 |
|---|---|---|
k6/http |
http.get, http.post, http.batch |
执行 HTTP 请求 |
k6 |
check, sleep, group |
断言、等待、事务分组 |
k6/metrics |
Rate, Trend, Counter, Gauge |
自定义指标 |
k6/encoding |
encoding.b64encode |
Base64 编解码 |
k6/crypto |
crypto.randomBytes |
加密与随机数据生成 |
3.2 export default 函数:VU 的执行入口
export default function () { ... } 是每个 VU 的执行入口。k6 运行时,每个 VU 会反复执行这个函数,每次执行称为一次 iteration (迭代)。这个函数内部通常包含:
- 业务操作:如 HTTP 请求、gRPC 调用、WebSocket 连接。
- 检查点:使用
check()验证响应是否符合预期。 - 思考时间:使用
sleep()模拟用户等待。 - 指标记录:向自定义指标添加数据点。
重要限制:
export default 函数内部不能使用 __VU 和 __ITER 之外的全局变量来跨迭代共享状态。每个迭代之间是隔离的。如果需要共享数据(如登录 ),必须使用 __VU 编号或 SharedArray。
3.3 options 配置:控制压测行为
export const options = { ... } 是脚本的全局配置对象,用于定义压测的执行参数、负载模型、阈值等。它是 k6 脚本的“控制面板”。
export const options = {
// 基础并发模型
vus: 50, // 并发 VU 数量
duration: '2m', // 总运行时长
// 阶段式负载(ramping)
stages: [
{ duration: '1m', target: 100 }, // 1分钟内线性增加到100 VU
{ duration: '3m', target: 100 }, // 保持100 VU 3分钟
{ duration: '1m', target: 0 }, // 1分钟内降为0 VU
],
// 阈值(thresholds)
thresholds: {
http_req_duration: ['p(95)<2000'], // 95%的请求响应时间低于2秒
http_req_failed: ['rate<0.01'], // 失败率低于1%
},
// 其他常用配置
iterations: 1000, // 总迭代次数(与 duration 互斥)
maxRedirects: 10, // 最大重定向次数
insecureSkipTLSVerify: false, // 是否跳过 TLS 验证
};
| 配置项 | 类型 | 说明 |
|---|---|---|
vus |
number | 并发虚拟用户数 |
duration |
string | 压测运行时长(如 '30s', '5m', '2h') |
stages |
array | 阶段式负载变化,每个阶段包含 duration 和 target |
iterations |
number | 总迭代次数(与 duration 二选一) |
thresholds |
object | 通过/失败条件,基于内置或自定义指标 |
scenarios |
object | 高级场景编排(可混合多种执行器) |
3.4 VU 与 iteration 概念
理解 VU (Virtual User) 和 iteration (迭代) 是掌握 k6 并发模型的关键。
- VU (虚拟用户):一个独立的 JavaScript 运行时实例,拥有自己的全局作用域。每个 VU 独立执行
export default函数。VU 之间默认不共享内存。 - Iteration (迭代):VU 执行一次
export default函数的过程。一个 VU 可以执行多次迭代。 - 关系:
总迭代次数 = VU 数量 × 每个 VU 执行的迭代次数。在duration模式下,每个 VU 在持续时间内尽可能多地执行迭代。
实战理解: 假设
vus: 10,duration: '1m',每个迭代耗时约 2 秒(含 sleep)。那么每个 VU 大约执行 30 次迭代,总迭代次数约为 300 次。k6 会尽力让所有 VU 在 1 分钟内持续运行。
3.5 生命周期钩子:setup 与 teardown
k6 提供了两个生命周期钩子函数,用于在压测开始前和结束后执行一次性任务:
export function setup():在压测开始前执行一次,用于初始化数据(如创建测试用户、获取 )。返回值会作为参数传递给export default函数。export function teardown(data):在所有 VU 执行完毕后执行一次,用于清理资源(如删除测试数据)。接收setup()的返回值。
import http from 'k6/http';
import { check } from 'k6';
// 初始化阶段:获取全局
export function setup() {
let res = http.post('https://api.example.com/login', {
username: 'test_user',
password: 'test_pass',
});
check(res, { 'login success': (r) => r.status === 200 });
return { : res.json('') }; // 传递给 default 函数
}
// 压测阶段:每个 VU 每次迭代使用
export default function (data) {
let headers = { Authorization: `Bearer ${data.}` };
let res = http.get('https://api.example.com/dashboard', { headers });
check(res, { 'dashboard ok': (r) => r.status === 200 });
}
// 清理阶段:删除测试数据
export function teardown(data) {
let headers = { Authorization: `Bearer ${data.}` };
http.del('https://api.example.com/test-data', null, { headers });
}
最佳实践:
setup() 和 teardown() 只执行一次,适合做全局准备和清理。不要在 setup() 中执行高耗时操作,因为它会阻塞压测启动。如果初始化耗时较长,考虑使用 SharedArray 或外部数据文件。
3.6 完整脚本结构示例
将上述所有要素组合在一起,形成一个完整的 k6 压测脚本:
// 1. 模块导入
import http from 'k6/http';
import { check, sleep, group } from 'k6';
import { Rate } from 'k6/metrics';
// 2. 自定义指标
const errorRate = new Rate('errors');
// 3. 全局配置
export const options = {
stages: [
{ duration: '30s', target: 20 },
{ duration: '1m', target: 20 },
{ duration: '30s', target: 0 },
],
thresholds: {
errors: ['rate<0.05'],
http_req_duration: ['p(95)<3000'],
},
};
// 4. 初始化钩子
export function setup() {
let res = http.post('https://api.example.com/login', {
username: 'perf_user',
password: 'perf_pass',
});
return { : res.json('access_') };
}
// 5. VU 入口
export default function (data) {
group('用户浏览商品', function () {
let headers = { Authorization: `Bearer ${data.}` };
let res = http.get('https://api.example.com/products', { headers });
let success = check(res, { 'products status 200': (r) => r.status === 200 });
errorRate.add(!success);
sleep(1);
});
group('用户下单', function () {
let payload = JSON.stringify({ product_id: 123, quantity: 1 });
let headers = {
'Content-Type': 'application/json',
Authorization: `Bearer ${data.}`,
};
let res = http.post('https://api.example.com/orders', payload, { headers });
let success = check(res, { 'order status 201': (r) => r.status === 201 });
errorRate.add(!success);
sleep(2);
});
}
// 6. 清理钩子
export function teardown(data) {
let headers = { Authorization: `Bearer ${data.}` };
http.del('https://api.example.com/logout', null, { headers });
}
脚本执行顺序总结:
1. k6 解析
2. 执行
3. 启动 N 个 VU,每个 VU 反复执行
4. 所有 VU 执行完毕后,执行
1. k6 解析
options 和 import 模块。2. 执行
setup() 一次,获取初始化数据。3. 启动 N 个 VU,每个 VU 反复执行
export default(data),直到达到 duration 或 iterations 限制。4. 所有 VU 执行完毕后,执行
teardown(data) 一次进行清理。