k6持续发送指定并发HTTP请求时性能不达预期的问题排查
看起来你遇到的问题核心是k6单线程运行时的事件循环阻塞加上测试配置(Iterations)的错误设置,导致请求发送速率远低于预期。我来一步步拆解问题原因,再给出针对性的修复方案:
一、问题根源分析
1. 错误的Iterations配置限制了测试执行次数
你在options里设置了iterations: 12 + vus:12,这意味着整个测试只会执行12次迭代(每个VU刚好跑1次)。虽然你在default函数里用while(Date.now() - startTime < duration)试图让测试持续10秒,但k6的迭代模型中,单个迭代的执行逻辑会被事件循环的单线程特性限制,没办法高效持续发起异步请求。
2. 同步while循环阻塞了k6的事件循环
k6的JavaScript运行时是单线程的,你在default函数里写的同步while循环会持续占用线程资源。哪怕用了http.asyncRequest(异步请求),但发起请求的for循环、while循环的判断都是同步操作,会卡住事件循环,导致k6无法及时调度新的异步请求,甚至无法处理已发起请求的回调逻辑,最终请求发送速率被严重限制。
3. 手动速率控制的精度极低
你试图用while循环 + 可选SLEEP_INTERVAL来控制每秒100次请求,但这种手动方式完全依赖同步代码的执行速度,既不精准,又容易触发单线程瓶颈。当你把NUM_EVENTS调到1000时,单次循环要发起1000个请求,k6的单线程根本处理不过来,导致1秒和10秒的总请求数没区别——本质是线程已经被占满,没办法发起更多请求了。
二、修复方案:用k6原生特性重构测试
我们需要抛弃手动while循环的方式,改用k6推荐的Scenarios(场景)和速率控制函数来实现精准的请求发送逻辑,同时避免事件循环阻塞。
1. 核心修改思路
- 移除
iterations配置,改用duration或Scenario控制测试时长 - 用k6内置的速率控制(如
rate()函数或Scenario的arrival-rateexecutor)来精准控制每个VU每秒发送100次请求 - 保持每个VU对应一个URL的映射逻辑,避免资源竞争
2. 修复后的完整代码示例
import { open } from 'k6/x/file'; import { parseCSV } from 'https://jslib.k6.io/k6-utils/1.4.0/index.js'; import { check, rate } from 'k6'; import encoding from 'k6/encoding'; import http from 'k6/http'; export let options = { vus: 12, // 12个VU对应12个URL duration: '10s', // 测试总时长10秒,替代手动while循环的时长判断 insecureSkipTLSVerify: true, thresholds: { http_req_duration: ['p(95)<2000'], http_req_failed: ['rate<0.01'] } }; // **API Endpoints & Authentication for Different VUs** const CREDENTIALS = [ { url: 'url1', username: 'user1', password: 'pass1' }, { url: 'url2', username: 'user2', password: 'pass2' }, // ... 补充剩下10个配置 ]; // 确保数组长度为12 const NUM_EVENTS_PER_SECOND = 100; const ENTITY_PREFIX = "ABC"; const getResponse = false; // 加载测试数据(请替换为你的CSV路径) const parsedCSV = parseCSV(open('./your-test-data.csv')); // **Main Test Execution** export default function () { // 每个VU对应唯一的凭证 const userIndex = (__VU - 1) % CREDENTIALS.length; const API_URL = CREDENTIALS[userIndex].url; const USERNAME = CREDENTIALS[userIndex].username; const PASSWORD = CREDENTIALS[userIndex].password; // 用k6的rate函数严格控制每秒发送100次请求 rate(NUM_EVENTS_PER_SECOND); // 循环复用测试数据(避免数据耗尽) const dataIndex = (__ITER - 1) % parsedCSV.length; let event = parsedCSV[dataIndex]; // 处理请求payload event.entity = `${ENTITY_PREFIX}_${event.entity}`; event.entityid = event.entity; event.parameterid = event.parametername; let payload = JSON.stringify(event); // 构造请求参数 let params = { headers: { 'Content-Type': 'application/json', 'Authorization': 'Basic ' + encoding.b64encode(`${USERNAME}:${PASSWORD}`) }, insecureSkipTLSVerify: true, }; // 发起异步请求 let request = http.asyncRequest('POST', API_URL, payload, params); // 可选:处理响应(如果需要) if (getResponse) { request.then(res => { console.log(`🔍 VU ${__VU} | Response ${__ITER} | Status: ${res.status}`); check(res, { "is status 200": (r) => r.status === 200 }); }).catch(error => { console.error(`❌ VU ${__VU} | Request ${__ITER} failed: ${error.message}`); }); } }
3. 额外优化建议
如果需要更精准的请求到达率:可以改用k6的
constant-arrival-rate场景执行器,它能严格控制每秒发起的请求总数,适合需要精准流量模型的测试。示例配置如下:export let options = { insecureSkipTLSVerify: true, thresholds: { http_req_duration: ['p(95)<2000'], http_req_failed: ['rate<0.01'] }, scenarios: { targeted_request_rate: { executor: 'constant-arrival-rate', rate: 1200, // 总请求率:12个URL * 100请求/秒 = 1200请求/秒 timeUnit: '1s', // 速率单位 duration: '10s', // 测试时长 preAllocatedVUs: 12, // 预分配12个VU对应12个URL maxVUs: 12, // 最大VU数和预分配一致,避免动态扩容 }, }, };这种模式下,每个VU会平均承担100请求/秒的压力,刚好匹配你的需求。
避免测试数据不足:确保
parsedCSV的长度至少是10秒 * 100请求/秒 = 1000条每个VU,或者在代码里做好数据循环复用的逻辑,避免因数据耗尽导致请求中断。关闭不必要的响应处理:如果
getResponse为false,完全可以去掉request.then()的逻辑,减少事件循环的回调处理压力,进一步提升请求发送效率。
三、验证效果
修改完成后,你可以直接运行脚本查看k6终端输出的http_reqs指标,正常情况下应该能达到12 * 100 *10 = 12000次请求的预期。如果还是有瓶颈,可以检查本地机器的CPU/网络资源(k6是单线程工具,高并发场景下可能需要多实例分布式测试)。
备注:内容来源于stack exchange,提问作者sarath chandra

