跳转至

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 用的是特殊 JSk6 不支持 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