k6是一款由Grafana Labs维护的开源负载测试工具,它使用JavaScript编写测试脚本,能够以极低的资源开销模拟大量虚拟用户并发访问目标服务。对于React应用来说,虽然k6无法直接测试组件渲染性能,但React应用所依赖的后端API才是并发压力的主要承载体。通过k6对登录、列表查询、提交表单等高频接口进行压测,可以在上线前评估服务端的承载能力,找到响应时间陡增的临界点,为扩容和优化提供数据依据。

一、k6的安装与核心概念
k6支持Windows、macOS和Linux三大平台。Windows用户可以通过Chocolatey快速安装,执行choco install k6即可;macOS用户使用brew install k6;Linux用户也可以直接下载官方二进制包。安装完成后在终端执行k6 version,能正常输出版本号就说明环境已经就绪。
在动手写脚本之前,需要先理解k6的几个核心概念。第一个是VU(Virtual User),也就是虚拟用户,每个VU是一个独立的执行循环,可以理解为模拟一个真实用户在反复执行操作。第二个是iterations,即迭代次数,VU每执行一遍init、setup、默认函数就算一次迭代。第三个是executor,也就是执行器,它决定了VU如何被调度,比如固定并发数、逐步爬坡、恒定到达率等模式,压测策略主要就靠它来配置。
一个典型的k6脚本包含三个生命周期阶段:init代码在脚本最外层执行,用于导入模块和定义选项;setup函数在测试开始时执行一次,适合做登录获取token等准备工作;默认导出的函数则是每个VU反复执行的主体逻辑。此外还有teardown函数用于测试结束后的清理。理解了这个结构,后面编写复杂场景脚本就会顺畅很多。
二、编写第一个针对React应用API的压测脚本
假设React应用的后端提供了一组典型的REST接口:登录接口返回token,商品列表接口支持分页查询,下单接口需要携带认证信息。下面这段脚本模拟了完整的用户行为链路,先登录拿到token,再查询列表,最后执行一次下单操作。
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
vus: 50, // 50个虚拟用户
duration: '60s', // 持续压测60秒
};
export function setup() {
// 测试前的准备工作,只执行一次
const res = http.post('http://127.0.0.1:8080/api/login', JSON.stringify({
username: 'testuser',
password: 'test123456',
}), { headers: { 'Content-Type': 'application/json' } });
return { token: res.json('data.token') };
}
export default function (data) {
const headers = {
'Content-Type': 'application/json',
'Authorization': `Bearer ${data.token}`,
};
// 查询商品列表
const listRes = http.get('http://127.0.0.1:8080/api/products?page=1&size=20', { headers });
check(listRes, {
'列表接口状态码为200': (r) => r.status === 200,
'响应时间小于800ms': (r) => r.timings.duration < 800,
});
// 模拟用户思考时间
sleep(1);
// 提交订单
const orderRes = http.post('http://127.0.0.1:8080/api/orders', JSON.stringify({
productId: 101,
quantity: 1,
}), { headers });
check(orderRes, {
'下单接口状态码为200': (r) => r.status === 200,
});
sleep(2);
}这段脚本中有几个值得注意的细节。setup函数中获取的token会通过返回值传递给每个VU的默认函数,避免了每个虚拟用户重复登录造成的额外压力,这更贴近真实场景中的会话复用。但如果你的目标是专门压测登录接口本身,那就应该把登录逻辑放到默认函数里。sleep调用模拟了真实用户浏览页面时的停顿,没有停顿的压测相当于机器人请求,得出的结论会偏悲观。
check函数类似断言,但与断言不同的是它不会中断测试,只会把结果统计到报告里。可以在输出的指标中看到每个check的通过率,如果某个校验项通过率低于100%,说明服务在高压力下出现了异常行为,这种问题在低并发时往往是发现不了的。
三、执行策略进阶:爬坡测试与阈值控制
固定并发数的模式适合做基准测试,但想找到系统的性能拐点,就需要使用爬坡策略。k6提供了多种执行器,其中ramping-vus可以逐步增加并发用户,观察指标在哪个区间开始恶化。
export const options = {
scenarios: {
ramping_test: {
executor: 'ramping-vus',
startVUs: 0,
stages: [
{ duration: '30s', target: 50 }, // 30秒内爬到50并发
{ duration: '1m', target: 50 }, // 保持50并发1分钟
{ duration: '30s', target: 150 }, // 继续爬到150并发
{ duration: '1m', target: 150 }, // 保持150并发1分钟
{ duration: '30s', target: 0 }, // 逐步降为0
],
gracefulRampDown: '15s',
},
},
thresholds: {
// 95%的请求必须在1.5秒内完成,否则测试标记为失败
http_req_duration: ['p(95)<1500'],
// 错误率必须低于1%
http_req_failed: ['rate<0.01'],
},
};这段配置展示了两个重要能力。stages数组定义了完整的爬坡曲线,从50并发到150并发逐步加压,配合输出的折线数据可以清楚看到响应时间随并发增长的曲线,拐点出现的位置就是系统需要优化的信号。thresholds则定义了通过标准,一旦p95响应时间超过1.5秒或错误率超过1%,k6会以非零退出码结束,这个特性让它可以很自然地嵌入CI/CD流水线,作为性能回归的门禁。
除了ramping-vus,还经常用到constant-arrival-rus执行器,它按固定的请求到达率来施压,而不是固定并发数。这种方式能更准确地模拟目标吞吐量,比如要验证系统是否能扛住每秒500次请求,用到达率模式比并发数模式更直接,因为实际吞吐量取决于每个请求的耗时。
四、解读压测结果与常见性能陷阱
压测跑完后,k6会在终端输出一组关键指标。http_req_duration是请求总耗时,通常重点看p90、p95和p99而不仅仅是平均值,平均值容易被少量慢请求拉高或掩盖问题。http_req_failed统计失败请求比例。http_req_connecting和http_req_tls_handshake分别反映TCP连接和TLS握手耗时,如果这两项占比很高,说明瓶颈可能不在业务逻辑而在连接建立上。
对React应用的后端做压测时,有几个常见陷阱需要留意。第一是测试环境与生产环境的差异,如果压测环境的数据库数据量只有几千条而生产是几百万条,查询接口的结果就没有参考意义。第二是连接池配置,压测时经常遇到数据库连接池打满导致响应时间暴增,这本身就是一个有价值的发现,但需要区分是业务瓶颈还是配置瓶颈。第三是压测机的出口带宽和CPU限制,如果压测机本身先达到瓶颈,得到的QPS数据是被低估的。
最后建议把k6脚本纳入版本管理,并定期在预发布环境执行。配合Grafana面板展示历史趋势,团队可以直观看到每次代码变更对性能的影响,让性能优化从凭感觉变成看数据,这才是负载测试真正的价值所在。