1. 项目概述:为什么我们需要一个现代化的性能测试方案?

如果你是一名后端开发、运维或者测试工程师,性能测试这个词对你来说一定不陌生。从早期的 Apache Bench (ab),到后来几乎成为行业标准的 JMeter,我们似乎有足够多的工具可以选择。但为什么我们今天还要聊 k6?答案很简单:因为传统的性能测试工具,在今天的云原生、微服务、持续集成/持续交付(CI/CD)时代,已经显得有些“笨重”了。

我经历过很多次这样的场景:开发团队需要快速验证一个 API 接口的并发能力,或者运维团队想在上线前给新服务做个压力摸底。大家的第一反应往往是:“上 JMeter 吧”。然后就是下载、安装 Java 环境、打开那个略显复杂的 GUI、录制脚本、配置线程组、添加各种监听器……一套流程下来,半小时过去了,测试还没开始。更别提把测试脚本纳入版本管理、在 CI 流水线中自动运行了。JMeter 功能强大,但它更像一个“桌面重型武器”,而不是可以随身携带、快速部署的“突击步枪”。

k6 的出现,正好填补了这个空白。它是一个用 Go 语言编写的开源负载测试工具,最大的特点就是 开发者友好 云原生友好 。它的测试脚本用 JavaScript (ES6+) 编写,对前端和 Node.js 开发者来说几乎没有学习成本;它本身是一个命令行工具,轻量、无依赖,可以轻松集成到任何 CI/CD 流程中;更重要的是,它内置了对现代协议(如 HTTP/2, WebSocket, gRPC)的良好支持,并且能通过丰富的输出选项,生成我们梦寐以求的、清晰直观的可视化报告。

所以,这个项目的核心,就是带你彻底掌握 k6,从编写第一个简单的脚本开始,到设计复杂的混合场景,再到将测试结果转化为一份份可以直接用于汇报、分析的“优雅报告”,构建一套高效、可重复、可集成的现代化性能测试工作流。

2. 核心思路与工具选型:为什么是 k6,而不是 JMeter 或 ab?

在决定投入时间学习一个新工具前,我们必须搞清楚它的定位和优势。这里我将 k6 与几个常见的工具做一个横向对比,你就能明白在什么场景下 k6 是你的最佳选择。

2.1 工具对比:找到你的“瑞士军刀”

特性维度 k6 Apache JMeter Apache Bench (ab) Locust
脚本语言 JavaScript (ES6+) Java (GUI/XML) 无(命令行参数) Python
架构与部署 单二进制文件,无依赖 需要 Java 环境,GUI/无头模式 单命令行工具 主从架构,需要 Python
学习曲线 (对开发者友好) 中高(GUI复杂,概念多) 极低 (参数简单) 中(需 Python 基础)
协议支持 HTTP/1.1, HTTP/2, WebSocket, gRPC (通过扩展) 极其广泛(HTTP, FTP, JDBC, JMS等) 仅 HTTP/1.0, HTTP/1.1 HTTP, WebSocket (可自定义)
测试场景建模 (代码控制,灵活度高) 强(通过逻辑控制器) 弱(仅简单并发) 强(代码控制)
CI/CD 集成 优秀 (CLI优先,输出格式丰富) 一般(需处理 XML 和日志) 简单(解析控制台输出) 良好(可通过命令运行)
结果分析与报告 优秀 (内置多种输出,社区仪表板丰富) 依赖监听器,报告需额外处理 极简(控制台文本) 自带 Web UI,报告需自定义
资源消耗 低(Go 编译,高效) 高(Java 进程,内存占用大) 极低 中(Python 解释器)
社区与生态 活跃,由 Grafana Labs 赞助 非常庞大和成熟 稳定但古老 活跃

我的选型心得:

  • 当你需要快速、轻量地测试 HTTP API,并集成到 CI 中时,放弃 ab,选择 k6。 ab 太简单了,只能做最基础的并发测试,无法模拟复杂的用户思考时间、登录流程或参数化。
  • 当你面对的是一个由开发者主导、需要频繁在 CI 中运行性能测试的微服务项目时,放弃 JMeter,选择 k6。 JMeter 的 XML 脚本难以进行版本控制和代码评审,其资源消耗在容器化环境中也显得臃肿。k6 的 JavaScript 脚本则是“一等公民”,可以享受所有现代开发工具链的好处。
  • 当你需要测试 WebSocket 或 gRPC 服务时,k6 的现代协议支持是天然优势。 虽然 JMeter 和 Locust 通过插件也能实现,但 k6 的支持更原生、更简洁。
  • 只有当你需要测试像 FTP、JDBC 这类非常传统的协议,或者团队已有深厚的 JMeter 资产和知识积累时,才继续使用 JMeter。

简单来说, k6 是为云原生时代的工程师和 DevOps 流程量身定做的性能测试工具。

2.2 k6 的核心架构与关键概念

理解 k6 的工作方式,能帮助你更好地设计测试。它的运行模型非常清晰:

  1. 初始化阶段 ( init ) : 在虚拟用户(VU)开始执行前运行一次。通常在这里导入模块、加载测试数据(如从 CSV 或 JSON 文件读取)、定义全局配置。这部分代码不计入测试时间。
  2. 虚拟用户(VU)脚本 : 这是测试的主体,定义每个虚拟用户的行为。它包含一个默认导出的函数。
  3. 默认函数 ( export default function ) : 这个函数会被每个 VU 反复执行。你在这里定义主要的业务逻辑:发送请求、检查响应、记录自定义指标、模拟用户等待时间等。
  4. 清理阶段 ( teardown ) : 在所有 VU 执行完毕后运行一次。用于关闭连接、清理资源等。

k6 通过 options 对象来控制负载模型,这是它的精髓之一。你可以定义阶段化的负载,例如:先从 0 个用户 ramp-up 到 10 个用户,持续 30 秒,再 ramp-up 到 50 个用户,持续 1 分钟,最后 ramp-down。这种能力对于模拟真实世界的流量波动(如秒杀场景)至关重要。

3. 从零开始:编写你的第一个 k6 测试脚本

理论说再多,不如动手写一行代码。让我们从一个最简单的 HTTP GET 请求测试开始。

3.1 环境准备与安装

k6 的安装简单到令人发指。前往 k6.io 官网 选择对应你操作系统的安装方式。以 macOS 为例,一行命令搞定:

brew install k6

安装完成后,在终端输入 k6 version ,看到版本号即表示成功。不需要配置环境变量,不需要安装运行时,它就是一个独立的可执行文件。

3.2 基础脚本解析:一个完整的例子

创建一个名为 test.js 的文件,输入以下内容:

import http from 'k6/http';
import { check, sleep } from 'k6';

// 1. 初始化阶段:加载测试数据(这里我们硬编码一个用户)
export let options = {
  stages: [
    { duration: '30s', target: 10 }, // 在30秒内逐渐增加到10个并发用户
    { duration: '1m', target: 10 },  // 保持10个用户1分钟
    { duration: '30s', target: 0 },  // 在30秒内逐渐减少到0个用户
  ],
  thresholds: {
    'http_req_duration': ['p(95)<500'], // 95%的请求响应时间应小于500ms
    'http_req_failed': ['rate<0.01'],   // 请求失败率应小于1%
  },
};

// 2. 虚拟用户主逻辑
export default function () {
  // 发送一个GET请求到我们的测试目标
  let res = http.get('https://httpbin.test.k6.io/get');

  // 使用 `check` 函数对响应进行断言,这会被记录为自定义指标
  check(res, {
    '状态码是200': (r) => r.status === 200,
    '响应体包含特定字段': (r) => r.json().hasOwnProperty('url'),
  });

  // 模拟用户思考时间,每个VU在请求后等待1秒
  sleep(1);
}

逐行解读与实操要点:

  • import 语句 : k6 采用 ES6 模块系统。 http 模块是核心,用于发送请求。 check sleep 是常用的工具函数。
  • options 对象 : 这是测试的“大脑”。
    • stages : 定义了负载模型。这个例子模拟了一个“爬坡-稳定-下坡”的经典场景,非常贴近真实用户访问的启动和退出过程。 注意: target 指的是并发虚拟用户数,不是每秒请求数(RPS)。RPS 会随着你的脚本逻辑( sleep 时间、请求耗时)动态变化。
    • thresholds : 阈值 是 k6 一个极其强大的功能。它定义了测试成功的标准。如果阈值被违反,k6 会以非零状态码退出,这可以直接让 CI/CD 流水线失败。上面配置的意思是:95% 的请求耗时必须在 500ms 以内,且请求失败率必须低于 1%。
  • export default function : 这是每个 VU 的“一生”。它会循环执行这个函数,直到测试结束(由 stages 的总时长控制)。
  • check 函数 : 它不仅是断言,更是 自定义指标 的来源。所有 check 的结果都会被 k6 收集,并可以在报告中看到通过率。这是验证业务逻辑正确性的关键。
  • sleep(1) : 千万不要忽略思考时间! 在性能测试中,连续不断地发送请求(“炮轰”模式)是一种非常不真实的负载,通常只会压垮服务器,而无法反映真实用户体验。加入随机的 sleep 时间(例如 sleep(Math.random() * 2 + 1) )能更好地模拟真人操作。

3.3 运行测试并解读基础输出

在终端中,进入脚本所在目录,运行:

k6 run test.js

你会看到类似下面的实时输出:

          /\      |‾‾| /‾‾/   /‾‾/
     /\  /  \     |  |/  /   /  /
    /  \/    \    |     (   /   ‾‾\
   /          \   |  |\  \ |  (‾)  |
  / __________ \  |__| \__\ \_____/ .io

  execution: local
     script: test.js
     output: -

  scenarios: (100.00%) 1 scenario, 10 max VUs, 1m30s max duration (incl. graceful stop)
           * default: Up to 10 looping VUs for 1m0s over 3 stages (gracefulRampDown: 30s, gracefulStop: 30s)

running (1m00.1s), 00/10 VUs, 776 complete and 0 interrupted iterations
default ✓ [======================================] 00/10 VUs  30s  10 VUs  30s  10 VUs  30s  0 VUs

     ✓ 状态码是200
     ✓ 响应体包含特定字段

     checks.........................: 100.00% ✓ 1552      ✗ 0
     data_received..................: 1.8 MB  30 kB/s
     data_sent......................: 120 kB  2.0 kB/s
     http_req_blocked...............: avg=1.15ms   min=1µs      med=4µs      max=152.02ms p(90)=6µs      p(95)=9µs
     http_req_connecting............: avg=1.14ms   min=0s       med=0s       max=152.01ms p(90)=0s       p(95)=0s
     http_req_duration..............: avg=163.86ms min=147.75ms med=159.44ms max=324.75ms p(90)=176.69ms p(95)=185.43ms
       { expected_response:true }...: avg=163.86ms min=147.75ms med=159.44ms max=324.75ms p(90)=176.69ms p(95)=185.43ms
     http_req_failed................: 0.00%   ✓ 0         ✗ 776
     http_req_receiving.............: avg=78.33µs  min=13µs     med=71µs     max=1.22ms   p(90)=112µs    p(95)=134µs
     http_req_sending...............: avg=30.88µs  min=8µs      med=27µs     max=348µs    p(90)=45µs     p(95)=55µs
     http_req_tls_handshaking.......: avg=0s       min=0s       med=0s       max=0s       p(90)=0s       p(95)=0s
     http_req_waiting...............: avg=163.75ms min=147.67ms med=159.33ms max=324.64ms p(90)=176.6ms  p(95)=185.33ms
     http_reqs......................: 776     12.922148/s
     iteration_duration.............: avg=1.16s    min=1.15s    med=1.15s    max=1.33s    p(90)=1.18s    p(95)=1.19s
     iterations.....................: 776     12.922148/s
     vus............................: 1       min=1       max=10
     vus_max........................: 10      min=10      max=10

关键指标解读:

  • http_req_duration : 请求总耗时。这是我们最关注的性能指标之一。 p(95)=185.43ms 意味着 95% 的请求在 185.43 毫秒内完成。 注意: 这个值包含了网络传输时间。对于内网测试,这个时间主要反映服务端处理能力;对于公网 API 测试,则需要考虑网络波动。
  • http_reqs : 总请求数(776)和每秒请求数 RPS(12.92/s)。RPS 是衡量系统吞吐量的核心指标。
  • iterations : 总迭代数(每个 VU 执行一次 default 函数算一次迭代)和每秒迭代数。
  • checks : 自定义检查的通过率。100% 表示所有 check 都通过了。
  • http_req_failed : 请求失败率。0.00% 是理想情况。
  • vus : 虚拟用户数。这里显示的是结束时的数量,测试过程中会在 1 到 10 之间变化。

注意: 控制台输出虽然信息丰富,但不利于长期保存、对比和分享。这就是为什么我们需要“优雅的可视化报告”。

4. 进阶脚本技巧:构建真实的测试场景

一个简单的 GET 请求测试只是开始。真实的业务场景要复杂得多:用户登录、浏览商品、下单、支付。下面我们一步步构建一个更真实的测试场景。

4.1 参数化:让每个虚拟用户行为不同

让所有用户请求同一个 URL 和参数是不真实的。我们需要参数化。

方法一:使用共享数组(简单场景)

import http from 'k6/http';
import { SharedArray } from 'k6/data';

// 使用 SharedArray 在 VU 间高效共享只读数据
const users = new SharedArray('用户列表', function () {
  // 这里可以是从文件读取,如 JSON.parse(open('./users.json'));
  return [
    { username: 'user1', password: 'pass1' },
    { username: 'user2', password: 'pass2' },
    // ... 更多用户
  ];
});

export default function () {
  // 每次迭代随机选取一个用户
  let user = users[Math.floor(Math.random() * users.length)];
  console.log(`当前虚拟用户使用账号: ${user.username}`);

  // 使用这个用户的信息发送请求...
  let loginRes = http.post('https://api.example.com/login', JSON.stringify(user), {
    headers: { 'Content-Type': 'application/json' },
  });
  // ... 后续操作
}

方法二:使用 CSV 文件(数据量大时) 创建一个 users.csv

username,password,userId
test1,123456,1001
test2,abcdef,1002

在脚本中读取:

import papaparse from 'https://jslib.k6.io/papaparse/5.1.1/index.js';
import http from 'k6/http';

const csvData = new SharedArray('CSV 数据', function () {
  return papaparse.parse(open('./users.csv'), { header: true }).data;
});

export default function () {
  let user = csvData[Math.floor(Math.random() * csvData.length)];
  // 使用 user.username, user.password, user.userId
}

实操心得:参数化的陷阱

  1. 数据量要足够大 :如果你的并发用户数是 100,但参数列表只有 10 条,就会导致大量重复和数据竞争,测试结果失真。通常参数列表大小应是并发用户数的 5-10 倍。
  2. SharedArray 是只读的 :它会在初始化阶段加载所有数据到内存,并在所有 VU 间共享。这比每个 VU 都读取一次文件高效得多,但你也无法在测试中修改它。
  3. 敏感信息处理 :切勿将真实的用户名密码硬编码在脚本或 CSV 中提交到代码仓库。应该使用环境变量或 k6 的 --env 参数传入,或者读取一个被 .gitignore 忽略的本地配置文件。

4.2 会话管理:处理 Cookies 和 Token

现代应用大多使用有状态会话。k6 的 http 模块会自动管理 Cookie,就像浏览器一样。

import http from 'k6/http';
import { check } from 'k6';

export default function () {
  // 1. 登录,获取 token。k6 会自动保存响应中的 Set-Cookie
  let loginPayload = { username: 'test', password: 'test' };
  let loginRes = http.post('https://api.example.com/login', JSON.stringify(loginPayload), {
    headers: { 'Content-Type': 'application/json' },
  });

  check(loginRes, { '登录成功': (r) => r.status === 200 });

  // 假设登录后返回一个 JSON Web Token (JWT)
  let authToken = loginRes.json().token;

  // 2. 使用 token 访问需要认证的接口
  // 方式一:放在 Authorization 头(更常见于 API)
  let headers = {
    'Authorization': `Bearer ${authToken}`,
    'Content-Type': 'application/json',
  };

  // 方式二:k6 自动管理的 Cookie 会在后续请求中自动携带
  // 无需手动设置,除非是特殊的 Cookie 处理逻辑

  let profileRes = http.get('https://api.example.com/profile', { headers: headers });
  check(profileRes, { '获取资料成功': (r) => r.status === 200 });

  // 3. 登出(可选)
  let logoutRes = http.post('https://api.example.com/logout', null, { headers: headers });
}

关键点: http 模块的每个请求方法( get , post 等)都接受一个可选的参数对象,你可以在其中设置 headers , cookies , tags 等。对于需要手动管理的认证信息(如 JWT),使用 headers ;对于服务器通过 Set-Cookie 管理的会话,k6 会自动处理。

4.3 复杂场景编排:分组与事务

当测试一个完整的业务流程时,我们需要将多个步骤组织起来,并衡量整个流程的性能。

import http from 'k6/http';
import { check, group, sleep } from 'k6';

export default function () {
  // 使用 `group` 对步骤进行逻辑分组,并自动统计该组的耗时
  group('用户登录流程', function () {
    let loginRes = http.post('https://api.example.com/login', /* ... */);
    check(loginRes, { '登录成功': (r) => r.status === 200 });
    sleep(1);
  });

  group('浏览商品列表', function () {
    let listRes = http.get('https://api.example.com/products');
    check(listRes, { '获取列表成功': (r) => r.status === 200 && r.json().length > 0 });
    sleep(2);
  });

  // 模拟用户查看某个商品详情
  let productId = 123;
  group(`查看商品详情 ${productId}`, function () {
    let detailRes = http.get(`https://api.example.com/products/${productId}`);
    check(detailRes, { '获取详情成功': (r) => r.status === 200 });
    sleep(3);
  });

  // 使用 `check` 和自定义指标来定义“业务事务”
  // 但更清晰的做法是使用 `Trend` 指标手动记录(见下文)
}

group 的好处是,在最终的报告里,你会看到每个 group 的独立耗时统计( group_duration ),这能帮你快速定位业务流程中的性能瓶颈环节。

5. 生成优雅的可视化报告:从数据到洞察

k6 原生的控制台输出适合实时监控,但要做分析、写报告、做演示,我们需要更直观的图表。k6 本身不提供 GUI,但它提供了多种输出结果的方式,我们可以利用这些方式生成漂亮的报告。

5.1 输出到 JSON 文件与基础分析

最简单的持久化方式是将结果输出为 JSON 文件。

k6 run --out json=test_result.json test.js

这个 test_result.json 文件包含了所有指标的详细时间序列数据。你可以用任何编程语言(Python, Node.js)或工具(如 jq)来解析和分析它。但这仍然不够“优雅”。

5.2 使用 k6 Cloud 或 Grafana Cloud(付费/免费增值)

最强大、最省事的方案是使用官方云服务。k6 由 Grafana Labs 开发,与 Grafana 生态无缝集成。

步骤:

  1. Grafana Cloud 注册一个免费账户(包含一定额度的 k6 Cloud 用量)。
  2. 在本地安装 k6 后,使用 k6 login cloud 命令登录你的账户。
  3. 运行测试时,添加 --out cloud 参数。
k6 login cloud --token <YOUR_API_TOKEN>
k6 run --out cloud test.js

测试结束后,浏览器会自动打开一个云端的报告页面,或者你可以去 k6 Cloud 控制台查看。报告包括:

  • 交互式图表 :所有指标(RPS,响应时间,错误率,VU数)随时间变化的曲线。
  • 阈值状态 :清晰展示哪些阈值通过,哪些失败。
  • 请求分解 :详细列出每个请求的 URL、方法、状态码、耗时分布。
  • 测试结果对比 :可以方便地与历史测试结果进行对比。

优点 :开箱即用,功能全面,协作方便。 缺点 :免费额度有限,测试数据需上传到云端,可能涉及数据安全合规问题。

5.3 本地可视化方案:使用 k6-to-junit-xml html-report

对于需要本地化、私有化部署的团队,社区提供了优秀的方案。

方案一:生成 CI 友好的 JUnit XML 报告 许多 CI 系统(如 Jenkins, GitLab CI)可以解析 JUnit XML 格式的报告来展示测试结果。我们可以使用 k6-to-junit-xml 工具。

# 1. 运行测试,输出 JSON
k6 run --out json=result.json test.js

# 2. 使用 Node.js 工具转换
npx k6-to-junit-xml result.json -o report.xml

生成的 report.xml 可以被 CI 系统读取,在流水线页面中展示通过/失败的测试用例(对应你的 check 断言)和性能阈值状态。

方案二:生成独立的 HTML 报告(推荐) 这是我最喜欢的本地方案,能生成一个包含丰富图表的静态 HTML 文件,可以离线查看和分享。

我们可以使用社区工具 k6-html-reporter

首先,运行测试并输出 JSON 格式的摘要文件( summary.json ):

k6 run --summary-export=summary.json test.js

然后,使用该工具生成 HTML:

# 假设你已经通过 npm 全局安装了 k6-html-reporter
# npm install -g k6-html-reporter
k6-html-reporter --input summary.json --output report.html

打开 report.html ,你会看到一个包含以下内容的专业报告:

  • 测试概览 :总时长、总迭代数、总请求数、通过率。
  • 指标趋势图 :响应时间、RPS、虚拟用户数随时间的变化。
  • 阈值状态 :清晰的通过/失败标识。
  • 检查点结果 :列出所有 check 的通过情况。
  • 错误详情 :如果请求失败,会列出具体的错误信息。

我的实操心得:将报告生成集成到 CI 中 在 GitLab CI 或 GitHub Actions 的配置文件中,你可以这样写:

# .gitlab-ci.yml 示例
performance_test:
  stage: test
  image: grafana/k6:latest
  script:
    - k6 run --summary-export=summary.json script.js
    - |
      # 安装 Node 和报告生成器(如果基础镜像没有)
      apk add --no-cache nodejs npm
      npm install -g k6-html-reporter
      k6-html-reporter --input summary.json --output report.html
  artifacts:
    paths:
      - report.html
    expire_in: 1 week

这样,每次流水线运行后,你都可以直接下载 report.html 查看详细的性能测试报告,并将其作为发布决策的依据。

5.4 自定义指标与高级分析

有时内置的 HTTP 指标不够用。例如,你想跟踪一个“下单”事务的总耗时(可能包含多个 API 调用),或者记录某个特定业务逻辑的执行时间。这时就需要自定义指标。

import http from 'k6/http';
import { check, group } from 'k6';
import { Trend, Rate, Counter } from 'k6/metrics';

// 1. 定义自定义指标
let orderTransactionDuration = new Trend('order_transaction_duration');
let paymentSuccessRate = new Rate('payment_success_rate');
let cartAbandonmentCounter = new Counter('cart_abandonments');

export default function () {
  // ... 添加商品到购物车等操作 ...

  group('下单事务', function () {
    let startTime = Date.now(); // 记录开始时间

    // 步骤1: 创建订单
    let createOrderRes = http.post('/api/orders', /* ... */);
    check(createOrderRes, { '创建订单成功': (r) => r.status === 201 });

    // 步骤2: 支付
    let paymentRes = http.post('/api/payment', /* ... */);
    let paymentOk = check(paymentRes, { '支付成功': (r) => r.status === 200 });
    paymentSuccessRate.add(paymentOk); // 记录支付成功率

    // 步骤3: 确认订单
    if (paymentOk) {
      let confirmRes = http.put(`/api/orders/${orderId}/confirm`, /* ... */);
      check(confirmRes, { '确认订单成功': (r) => r.status === 200 });
    } else {
      cartAbandonmentCounter.add(1); // 支付失败,记录一次购物车放弃
    }

    let endTime = Date.now(); // 记录结束时间
    let transactionTime = endTime - startTime;

    // 2. 将事务耗时添加到 Trend 指标
    orderTransactionDuration.add(transactionTime);
  });
}

在这个例子中,我们定义了三种自定义指标:

  • Trend : 用于记录分布值,如耗时、大小。k6 会自动计算其平均值、分位数(p95, p99)等。
  • Rate : 用于记录比率,如成功率、失败率。 add(true) 算一次成功, add(false) 算一次失败。
  • Counter : 简单的累加计数器。

这些自定义指标会像内置指标一样,出现在最终的报告(控制台、JSON、HTML)中,让你能够从业务维度而不仅仅是技术维度来衡量系统性能。

6. 常见问题排查与性能测试经验谈

即使工具用得再熟,在实际压测过程中还是会遇到各种问题。下面是我总结的一些典型场景和排查思路。

6.1 问题排查清单

现象 可能原因 排查思路与解决方案
RPS 上不去,但 CPU/内存很低 1. 脚本中存在不合理的 sleep
2. 被测试服务有速率限制(Rate Limiting)
3. 客户端(k6运行机)成为瓶颈 (网络、端口数)。
4. 脚本逻辑有阻塞操作 (如同步文件读写)。
1. 检查 sleep 时间,在负载测试中可适当减少或移除思考时间进行极限压测。
2. 查看被测试服务日志,确认是否有 429 等状态码。调整限流策略或分批次测试。
3. 在 k6 运行机上运行 netstat 查看连接数,使用 top 查看资源。考虑分布式执行 k6。
4. 确保在 init 阶段加载所有文件数据,避免在 VU 代码中执行同步 IO。
响应时间( http_req_duration )异常高 1. 服务端处理慢 (数据库慢查询、代码低效)。
2. 网络延迟高 (特别是跨地域测试)。
3. 客户端连接池耗尽 ,等待建立新连接。
1. 监控服务端应用和数据库指标(CPU、慢查询日志、GC)。使用 http_req_waiting (服务器处理时间)与 http_req_duration 对比,如果两者接近,则是服务端问题。
2. 对比 http_req_connecting (TCP连接时间)和 http_req_tls_handshaking (HTTPS握手时间)。如果这两项占比高,则是网络/SSL问题。考虑在同地域网络测试。
3. 增加 k6 的 --max-connections --timeout 参数。
大量请求失败( http_req_failed 1. 服务端错误 (5xx)。
2. 客户端超时 http_req_timed_out 指标)。
3. 连接被拒绝 (服务端连接数满或未启动)。
4. SSL/TLS 问题
1. 查看 k6 输出中的具体错误信息,或增加 --verbose 标志。检查服务端日志。
2. 适当增加 --timeout 参数(默认 60s)。检查服务端处理能力。
3. 检查服务端是否存活,以及最大文件描述符数、TCP backlog 等系统参数。
4. 对于自签名证书,使用 insecureSkipTLSVerify: true 选项临时绕过验证。
内存使用量持续增长 1. k6 脚本内存泄漏 (在 VU 函数中不断创建全局变量)。
2. 被测试服务内存泄漏
1. 审查脚本,确保大型对象在 init 阶段用 SharedArray 加载,避免在 VU 循环内创建。使用 --compatibility-mode=base 禁用某些高内存消耗的 JS 特性。
2. 监控服务端内存使用情况。
checks 通过率低 1. 断言条件太严格或写错
2. 服务返回了非预期的业务状态
1. 打印出失败的响应内容进行调试: if (!check(res, {...})) { console.log(res.body); }
2. 确认接口契约,调整 check 逻辑。这可能发现了服务端的业务逻辑 Bug。

6.2 性能测试的“黄金法则”与避坑指南

  1. 永远不要在生产环境直接压测 :除非你有绝对的控制权和完备的回滚方案。使用独立的预发布/压测环境。压测流量可能引发雪崩,导致真实用户服务不可用。
  2. 循序渐进,从小开始 :不要一开始就上几百、几千的并发。从 1 个、10 个 VU 开始,观察系统响应和监控指标是否正常,再逐步增加。使用 stages 进行爬坡是非常好的实践。
  3. 监控,监控,再监控 :在压测过程中,必须同时监控被测试系统的所有层面:应用服务器(CPU、内存、线程池、GC)、数据库(连接数、慢查询、锁)、网络(带宽、连接数)、中间件(队列长度、缓存命中率)。没有监控的压测是盲人摸象。
  4. 设定明确的性能目标(SLA)和阈值 :在测试前就要和团队确定好:“我们的接口 p95 响应时间必须低于 200ms”,“登录接口的成功率必须高于 99.9%”。将这些目标转化为 k6 的 thresholds ,让测试结果有明确的“通过/失败”标准。
  5. 理解你的业务场景 :性能测试脚本不是简单的接口调用。要模拟真实用户行为:有登录登出、有浏览间隔、有搜索和下单的不同比例(例如,浏览:搜索:下单 = 10:3:1)。使用 group 和自定义指标来区分不同业务场景的负载。
  6. 关注趋势,而非单次结果 :性能测试的结果受环境影响(机器负载、网络波动、缓存状态)。一个重要的变更(如代码发布、数据库索引调整)后,应该运行相同的性能测试脚本多次,观察性能指标的变化趋势,而不是纠结于某一次测试的绝对数值。
  7. 分布式执行 :当单台机器无法产生足够压力,或者模拟来自全球不同地区的用户时,需要使用 k6 的分布式执行功能(如使用 k6-operator 在 Kubernetes 上运行,或使用多个 k6 run 实例配合 --out influxdb 将结果汇总)。

更多推荐