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