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 分钟内持续运行。
VU 与 Iteration 执行模型 VU #1 Iter 1 Iter 2 Iter 3 ... VU #2 Iter 1 Iter 2 Iter 3 ... VU #N Iter 1 Iter 2 Iter 3 ... 关键点: 每个 VU 拥有独立作用域,迭代之间默认隔离。VU 数量决定并发度,迭代次数决定总请求量。

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 或外部数据文件。
k6 生命周期执行流程 setup() 初始化(执行1次) VU 执行 default() 并发执行 N 次迭代 teardown() 清理(执行1次) 数据流: setup() 返回值 → 作为参数传递给每个 VU 的 default(data) → teardown(data) 接收同一份数据

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 解析 options 和 import 模块。
2. 执行 setup() 一次,获取初始化数据。
3. 启动 N 个 VU,每个 VU 反复执行 export default(data),直到达到 duration 或 iterations 限制。
4. 所有 VU 执行完毕后,执行 teardown(data) 一次进行清理。