3. k6 脚本结构:import、export default function、options 配置、VU 与 duration 概念、常用生命周期函数(setup/teardown)

在编写 k6 压测脚本时,理解其核心结构是进行 P95 阈值校准的前提。一个标准的 k6 脚本由 模块导入、全局配置、业务逻辑函数 以及 生命周期钩子 四部分组成。下面我们逐一拆解这些要素,并说明它们如何影响水位校准的精度。

3.1 脚本骨架:import 与 export default function

k6 脚本基于 JavaScript(ES6 模块)编写。所有脚本必须包含一个 export default function,这是虚拟用户(VU)执行的入口。外部模块通过 import 引入。

核心规则: export default function 中的代码会被每个 VU 在每次迭代中重复执行。不要在此函数外部定义与 VU 状态相关的变量,否则会导致数据污染。
import http from 'k6/http';
import { check, sleep } from 'k6';
import { Rate } from 'k6/metrics';

// 自定义指标:记录错误率
const errorRate = new Rate('errors');

export default function () {
  // 每个 VU 每次迭代执行的业务逻辑
  const res = http.get('https://test-api.example.com/api/endpoint');

  // 断言检查:响应状态是否为 200
  check(res, {
    'status is 200': (r) => r.status === 200,
  });

  // 记录错误
  errorRate.add(!res.error);

  // 思考时间:模拟用户操作间隔
  sleep(1);
}

3.2 options 配置:压测参数的声明式定义

export const options 是 k6 脚本的配置中心。它定义了压测的 负载模型、阈值、阶段 等关键参数。对于 P95 阈值校准,我们主要在此处配置 thresholds 和 stages。

配置项 类型 说明 校准场景示例
vus number 恒定虚拟用户数 固定 50 VU 进行基准测试
duration string 恒定负载运行时长 持续 5m 观察水位稳定性
stages array 分阶段负载递增/递减 从 0 逐步增加到 200 VU,寻找拐点
thresholds object 基于指标的通过/失败断言 http_req_duration[95] < 2000
ext object 扩展配置(如 k6-operator) 分布式执行时的资源限制
校准提示: 在 P95 阈值校准阶段,建议使用 stages 而非 vus + duration。因为分阶段递增可以更精确地定位系统性能的“水位线”,避免一次性施压导致系统雪崩。
export const options = {
  stages: [
    { duration: '2m', target: 50 },   // 热身:2分钟爬升到50 VU
    { duration: '5m', target: 50 },   // 稳定:保持50 VU运行5分钟
    { duration: '2m', target: 100 },  // 加压:2分钟爬升到100 VU
    { duration: '5m', target: 100 },  // 观察:保持100 VU
    { duration: '2m', target: 0 },    // 冷却:2分钟降为0
  ],
  thresholds: {
    // P95 响应时间不得超过 2 秒,且基于 10 个数据点评估
    'http_req_duration[95]': ['p(95)<2000', 'count>10'],
    // 错误率不得超过 1%
    'errors': ['rate<0.01'],
  },
};

3.3 VU 与 duration:理解虚拟用户与时间窗口

VU(Virtual User) 是 k6 中模拟并发用户的基本单位。每个 VU 独立运行 export default function 中的代码。Duration 定义了压测的总时长。

  • VU 数量 决定了并发度。在 P95 校准中,我们通常寻找“最大并发 VU 数”,使得 P95 响应时间仍处于可接受范围内。
  • Duration 必须足够长,以收集到具有统计意义的数据。对于 P95 阈值,建议每个阶段至少运行 2-3 分钟,确保采集到足够多的样本点。
  • 迭代次数 由 VU 数量、duration 和脚本中的 sleep() 共同决定。k6 会尽可能快地执行迭代,直到 duration 结束。
常见误区: 不要将 VU 数量等同于 QPS(每秒请求数)。QPS = (VU 数量) / (平均响应时间 + sleep 时间)。在 P95 校准中,我们关注的是 响应时间分布,而非单纯的吞吐量。

3.4 常用生命周期函数:setup / teardown

k6 提供了 setup() 和 teardown() 两个生命周期钩子,用于在压测前后执行一次性操作。它们对于 P95 校准中的数据准备和环境清理至关重要。

函数 执行时机 典型用途 校准场景示例
setup() 所有 VU 开始之前,仅执行一次 登录获取 、准备测试数据、预热缓存 批量创建 1000 个测试用户,避免压测时动态创建
teardown() 所有 VU 结束之后,仅执行一次 清理测试数据、关闭连接、生成汇总报告 删除压测期间创建的临时资源,避免影响下次校准
import http from 'k6/http';

// setup:压测前获取认证 
export function setup() {
  const loginRes = http.post('https://auth.example.com/login', {
    username: 'test_user',
    password: 'test_pass',
  });
  return { : loginRes.json('') };
}

// default function:使用 setup 返回的 
export default function (data) {
  const headers = { Authorization: `Bearer ${data.}` };
  http.get('https://api.example.com/protected/resource', { headers });
}

// teardown:压测后清理资源
export function teardown(data) {
  http.post('https://api.example.com/logout', {}, {
    headers: { Authorization: `Bearer ${data.}` },
  });
}

3.5 脚本结构全景图

下图展示了 k6 脚本的完整执行流程,以及各组件之间的协作关系。理解这张图有助于你在编写 P95 校准脚本时,合理分配代码逻辑。

k6 脚本执行全景图(P95 校准视角) setup() 一次性初始化 options 配置 vus / stages / thresholds VU 池(并发执行) VU #1 迭代 1, 2, 3... VU #2 迭代 1, 2, 3... VU #N 迭代 1, 2, 3... export default function HTTP 请求 + check + sleep 指标收集 & 阈值评估 P95 / 错误率 / 吞吐量 teardown() 校准关键点 • setup 准备测试数据 • stages 分阶段施压 • thresholds 定义 P95 水位线

3.6 脚本结构最佳实践(P95 校准专用)

  1. 将阈值与负载模型分离: 在 options 中只定义 thresholds 和 stages,不要将业务逻辑硬编码到配置中。
  2. 使用 setup 预热系统: 在 P95 校准前,通过 setup 发送少量请求“预热”缓存和连接池,避免冷启动影响 P95 数据。
  3. 避免在 default function 中定义全局变量: 每个 VU 独立运行,全局变量会导致竞态条件。使用 data 参数传递 setup 的返回值。
  4. 合理设置 sleep 时间: 模拟真实用户行为,避免“无脑”高压。通常设置为 0.5s ~ 3s 之间的随机值。
  5. 利用 teardown 清理资源: 每次校准后删除测试数据,确保下一次校准的环境干净。
实战建议: 在编写 P95 校准脚本时,先使用 k6 run --http-debug 验证脚本逻辑,确认 setup 和 default function 的数据传递正确,再执行正式压测。