k6 现代负载测试
一句话概述:k6 是 Grafana 出品的现代负载测试工具,用 JavaScript 写测试脚本,命令行运行,比 JMeter 轻量得多,特别适合开发者和 CI/CD 集成。
核心知识点
| 概念 | 白话解释 |
|---|
| VU(Virtual User) | 虚拟用户,每个 VU 独立运行你的测试脚本 |
| 迭代(Iteration) | 一个 VU 执行一次完整脚本就是一次迭代 |
| 场景(Scenario) | 定义负载模式,比如"逐渐增加到 100 用户" |
| 阈值(Threshold) | 性能指标的合格线,比如"P95 响应时间 < 500ms" |
| 检查(Check) | 验证响应是否正确,类似断言 |
| 阶段(Stage) | 负载变化的阶段,比如预热→满载→降载 |
安装配置
# macOS
brew install k6 # Homebrew 安装
# Linux (Debian/Ubuntu)
sudo gpg -k # 初始化 gpg
sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg \
--keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" \
| sudo tee /etc/apt/sources.list.d/k6.list # 添加源
sudo apt-get update && sudo apt-get install k6 # 安装
# Docker
docker run --rm -i grafana/k6 run - < script.js # Docker 运行
# Windows
choco install k6 # Chocolatey 安装
# 或者 winget install k6
基本使用
第一个测试脚本
// script.js - k6 测试脚本
import http from 'k6/http' // 导入 HTTP 模块
import { check, sleep } from 'k6' // 导入检查和延迟函数
// 测试配置
export const options = {
vus: 10, // 10 个虚拟用户
duration: '30s', // 运行 30 秒
}
// 默认函数:每个 VU 反复执行这个函数
export default function () {
const res = http.get('https://httpbin.org/get') // 发送 GET 请求
check(res, { // 检查响应
'状态码是200': (r) => r.status === 200, // 验证状态码
'响应时间小于500ms': (r) => r.timings.duration < 500, // 验证响应时间
})
sleep(1) // 模拟用户思考 1 秒(不加的话会拼命发请求)
}
# 运行测试
k6 run script.js # 执行测试脚本
阶段式负载(Ramp Up/Down)
// ramp-test.js - 逐步增加负载
export const options = {
stages: [ // 定义负载阶段
{ duration: '1m', target: 20 }, // 第1分钟:从 0 增加到 20 VU(预热)
{ duration: '3m', target: 20 }, // 接下来3分钟:保持 20 VU(稳定负载)
{ duration: '1m', target: 50 }, // 第5分钟:增加到 50 VU(压力测试)
{ duration: '3m', target: 50 }, // 保持 50 VU
{ duration: '1m', target: 0 }, // 最后1分钟:降到 0(冷却)
],
}
export default function () {
const res = http.get('http://localhost:3000/api/users')
check(res, {
'status 200': (r) => r.status === 200,
})
sleep(1)
}
POST 请求
import http from 'k6/http'
import { check } from 'k6'
export default function () {
const payload = JSON.stringify({ // 构造 JSON 请求体
name: '张三',
email: 'zhang@test.com',
})
const params = {
headers: {
'Content-Type': 'application/json', // 设置请求头
'Authorization': 'Bearer token123', // 认证令牌
},
}
const res = http.post( // 发送 POST 请求
'http://localhost:3000/api/users', // URL
payload, // 请求体
params, // 请求头
)
check(res, {
'创建成功': (r) => r.status === 201, // 验证 201 Created
'返回了ID': (r) => JSON.parse(r.body).id !== undefined, // 验证响应有 ID
})
}
高级用法
阈值(性能合格线)
export const options = {
vus: 50,
duration: '2m',
thresholds: { // 性能阈值,不达标则测试失败
http_req_duration: [
'p(95)<500', // 95% 的请求响应时间 < 500ms
'p(99)<1000', // 99% 的请求 < 1000ms
],
http_req_failed: ['rate<0.01'], // 错误率 < 1%
checks: ['rate>0.99'], // 检查通过率 > 99%
http_reqs: ['rate>100'], // 每秒请求数 > 100
},
}
场景(多种负载模式)
export const options = {
scenarios: {
// 场景1:浏览用户(恒定负载)
browse: {
executor: 'constant-vus', // 固定 VU 数
vus: 50, // 50 个虚拟用户
duration: '5m', // 持续 5 分钟
exec: 'browseProducts', // 执行的函数名
},
// 场景2:下单用户(逐步增加)
purchase: {
executor: 'ramping-vus', // 渐变 VU 数
startVUs: 0,
stages: [
{ duration: '2m', target: 20 },
{ duration: '3m', target: 20 },
],
exec: 'purchaseFlow', // 执行的函数名
},
},
}
// 浏览场景的函数
export function browseProducts() {
http.get('http://localhost:3000/api/products')
sleep(2)
}
// 下单场景的函数
export function purchaseFlow() {
http.post('http://localhost:3000/api/orders', JSON.stringify({
productId: 1,
quantity: 1,
}), { headers: { 'Content-Type': 'application/json' } })
sleep(3)
}
数据参数化
import { SharedArray } from 'k6/data' // 导入共享数组(VU 间共享,节省内存)
// 从 CSV/JSON 加载测试数据
const users = new SharedArray('users', function () {
return JSON.parse(open('./users.json')) // 读取 JSON 文件
})
export default function () {
const user = users[__VU % users.length] // 按 VU 编号轮流使用数据
const res = http.post('http://localhost:3000/api/login', JSON.stringify({
email: user.email,
password: user.password,
}), { headers: { 'Content-Type': 'application/json' } })
check(res, {
'登录成功': (r) => r.status === 200,
})
}
输出到 Grafana
# 输出到 InfluxDB(配合 Grafana 看实时监控面板)
k6 run --out influxdb=http://localhost:8086/k6db script.js
# 输出到 JSON 文件
k6 run --out json=result.json script.js
# 输出到 CSV
k6 run --out csv=result.csv script.js
常见报错
| 报错信息 | 原因 | 解决方案 |
|---|
WARN: some thresholds have failed | 性能指标没达标 | 优化被测服务或调整阈值 |
dial: connection refused | 服务没启动 | 确保被测服务可访问 |
too many open files | 系统文件描述符不够 | ulimit -n 65535 调高限制 |
request timeout | 响应太慢 | 检查服务性能或增大超时 |
| 内存不够 | VU 太多 | 使用分布式模式或减少 VU |
import 报错 | k6 用的是特殊 JS | k6 不支持 npm 包,只能用内置模块 |
速查表
# CLI 命令
k6 run script.js # 运行测试
k6 run --vus 100 --duration 30s script.js # 命令行指定参数
k6 run --out json=out.json script.js # 输出 JSON
k6 inspect script.js # 检查脚本配置
k6 cloud script.js # 上传到 k6 Cloud 运行
# 内置指标
http_req_duration # 请求总耗时
http_req_waiting # 等待响应耗时(TTFB)
http_req_connecting # TCP 连接耗时
http_req_failed # 失败请求比率
http_reqs # 请求总数/速率
vus # 当前虚拟用户数
iterations # 已完成迭代数
checks # 检查通过率
# 执行器类型
constant-vus # 固定 VU 数
ramping-vus # 渐变 VU 数
per-vu-iterations # 每个 VU 固定迭代次数
constant-arrival-rate # 固定到达率(恒定 RPS)
ramping-arrival-rate # 渐变到达率
# 阈值语法
'p(95)<500' # 95 百分位 < 500ms
'p(99)<1000' # 99 百分位 < 1000ms
'avg<200' # 平均值 < 200ms
'max<2000' # 最大值 < 2000ms
'rate<0.01' # 比率 < 1%
'rate>100' # 比率 > 100(用于 RPS)
参考:k6 官网 | k6 文档 | GitHub