k6 脚本一次写对:从零跑通性能压测
【免费下载链接】k6A modern load testing tool, using Go and JavaScript项目地址: https://gitcode.com/GitHub_Trending/k6/k6
压测跑到第 5 分钟,终端被 500 错误刷了满屏,结果里你设的错误率居然还是“通过”——阈值没挂在正确的指标名上。k6 是一个用 JavaScript 脚本驱动负载测试的工具,这类坑跟特性熟不熟无关,是脚本没当成一个整体来写。
三行代码,先让 k6 跑起来
你就想先确认目标服务通不通,结果卡在脚本第一行二十分钟。别急着写业务,先把这个三段式跑通:
import http from 'k6/http'; export function setup() { return { token: 'demo-token' }; // 测试前只跑一次 } export default function (data) { // 每轮迭代都执行,压测的主体 http.get('https://test-api.example.com/items', { headers: { Authorization: `Bearer ${data.token}` } }); } export function teardown(data) { console.log('run finished'); // 测试后只跑一次 }为什么是这三个导出:setup 做一次性准备,default 是每轮迭代的真实流量,teardown 收尾清理。k6 靠这三块就能跑,其余都是装饰。
能跑了,问题来了:你打算把 20 个接口和一串辅助函数全塞进这同一个文件。
脚本写长了怎么拆(k6 模块拆分)
脚本涨到 300 行、8 个接口,拼 header 的逻辑重复了四处,改一个字段像拆炸弹。拆不拆的判断标准其实就两条:两个接口共用同一段逻辑,或者单文件超过 100 行,满足任一条就拆。
| 来源 | 写法 | 典型例子 |
|---|---|---|
| 内置模块 | import http from 'k6/http' | http、metrics、data |
| 本地 .js | import { genUser } from './utils.js' | 业务辅助函数、参数拼装 |
| 外部扩展 | import redis from 'k6/x/redis' | k6/x/redis 等(构建期编译进二进制) |
把共用逻辑抽进一个 utils.js:
// utils.js:多个接口共用 export function genUser() { return { name: `user_${Math.random().toString(36).slice(2)}`, age: 18 }; } export const headers = { 'Content-Type': 'application/json' };主脚本里一句import { genUser, headers } from './utils.js',之后只关心请求本身。
文件拆好了,可流程不串起来,数据还是接不上:真人用户不是一个 GET 就完事的。
像真人一样点按钮
你的测试全是绿的 GET,但真人会先登录、再改、再读、最后登出,每一步的数据要递给下一步。四步串成一条链,公共请求参数放进一个对象里复用:
import http from 'k6/http'; const base = 'https://api.example.com'; const params = { // 四个请求共用 headers: { Authorization: `Bearer ${__ENV.TOKEN}` }, timeout: '10s', tags: { scenario: 'edit-flow' }, }; export default function () { const login = http.post(`${base}/login`, { user: 'a', pass: 'b' }, params); const id = JSON.parse(login.body).id; // 把数据递给下一步 http.put(`${base}/users/${id}`, { name: '新名字' }, params); const got = http.get(`${base}/users/${id}`, params); http.del(`${base}/logout`, null, params); }循环里每个请求都走同一个节奏:
校验就两个动作,check()管壳(状态码、耗时),res.json()管瓤(业务字段):
import { check } from 'k6'; check(got, { '状态码 200': (r) => r.status === 200, '耗时 < 500ms': (r) => r.timings.duration < 500, }); const name = got.json().name; // 把 body 当 JSON 解析 check(name, { '字段非空': (v) => !!v });流程齐了,可压测一结束,用什么判断过没过?光看终端刷得飞,不算数。
告诉 k6 什么算达标:阈值与场景编排
跑完所有人问“过没过”,你只能说“看着挺快”——达标线必须写进脚本,否则 k6 只能告诉你“测完了”,不能告诉你“达标了”。k6 阈值怎么写,八成场景就两类表达式:响应时间用p(95),错误率用rate。
export const options = { scenarios: { stable: { executor: 'constant-vus', vus: 10, duration: '10m' }, // 恒定 VU ramp: { executor: 'ramping-vus', startVUs: 10, stages: [ // 阶梯加压 { duration: '5m', target: 100 }, { duration: '3m', target: 100 }, { duration: '1m', target: 0 }, ] }, }, thresholds: { http_req_duration: ['p(95)<500'], // 95% 的请求 < 500ms http_req_failed: ['rate<0.01'], // 错误率 < 1% }, };两种执行器放一起对比,选型不纠结:
| 执行器 | VU 行为 | 适用场景 |
|---|---|---|
| constant-vus | 人数钉死不动 | 稳态浸泡,跑满 10 分钟看劣化 |
| ramping-vus | 按 stages 台阶升降 | 找峰值、找拐点 |
阈值和场景在同一个options里,改负载不用动校验逻辑。仓库里还有现成例子可抄:examples/thresholds.js、examples/stages.js。
阈值定了、场景排了,可 VU 一上量,一行await漏写,整场压测就乱了。
让并发真正跑起来:k6 异步与 Promise
并发一上量,终端开始刷“token undefined”——这不是并发的问题,是时序的问题。嵌套回调不顺手的话,直接改写成 async/await:
// 老式 .then():每跳一层多一层嵌套 http.get(url).then((r) => r.json()).then((d) => use(d)); // async/await:从上往下读,一个 catch 兜住所有错误 const d = await (await http.get(url)).json(); use(d);服务不稳时,两个小函数能救命:
import { sleep } from 'k6'; async function withTimeout(p, ms) { // 3 秒没结果就放弃 return Promise.race([p, new Promise((_, rej) => setTimeout(() => rej(new Error('timeout')), ms))]); } async function retry(fn) { // 最多 3 次,1s/2s 退避 for (let i = 1; ; i++) { try { return await fn(); } catch { if (i === 3) throw new Error('retry exhausted'); await sleep(i); } } }一句话分清:sleep(1)是真等 1 秒、会算进测试时长;await一个请求只是等这次请求的结果,前者是时间,后者是依赖。
脚本全写完了,按下回车前的最后 5 分钟,值得把清单翻一遍。
压测前 5 分钟自检
终端已经预热好了,发车前 30 秒对照这几条:
- 阈值设了吗?
http_req_duration和http_req_failed至少各挂一条 - 阈值破了要不要中止全场?要的话用对象形式阈值加
abortOnFail: true - 请求异常被 try/catch 兜住了?没兜住的错误只会在汇总里变成 VU 失败率突变
- 场景时长 ≥ 5 分钟?30 秒的数据,百分位数没有统计意义
- token 走
__ENV而不是硬编码?⚠️ 脚本会进版本库 - ramping-vus 降载后留了
gracefulRampDown,让在途请求跑完 - 先用 1 个 VU 冒烟,流程完整走通一次再放大 VU 数
清单过完再回车。这次跑完的脚本,下次换个环境直接复用。
【免费下载链接】k6A modern load testing tool, using Go and JavaScript项目地址: https://gitcode.com/GitHub_Trending/k6/k6
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考