Node.js 应用上线前,压测是验证承载能力的关键。模拟真实用户并发访问,能发现性能瓶颈、评估系统极限。这篇介绍 Node.js 生态里比较好用的压测工具 loadtest,以及 TPS、RT、P90/P95/P99 这些指标的含义。
压力测试核心指标
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| ┌─────────────────────────────────────────────────────────────────────┐ │ 压力测试核心指标说明 │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ TPS (Transactions Per Second) │ │ ───────────────────────────── │ │ • 每秒处理事务数(包括成功、失败、超时) │ │ • 衡量系统吞吐量的核心指标 │ │ • TPS = 总请求数 / 总时间 │ │ │ │ 响应时间 (Response Time) │ │ ───────────────────────── │ │ • 平均响应时间:所有请求的平均耗时 │ │ • 最大响应时间:最慢请求的耗时 │ │ • 最小响应时间:最快请求的耗时 │ │ │ │ 百分位响应时间 (Percentiles) │ │ ───────────────────────────── │ │ • P90:90% 的请求响应时间小于此值 │ │ • P95:95% 的请求响应时间小于此值 │ │ • P99:99% 的请求响应时间小于此值 │ │ • 排除极端异常值,反映大多数用户体验 │ │ │ └─────────────────────────────────────────────────────────────────────┘
|
| 指标 |
英文名 |
说明 |
优化目标 |
| TPS |
Transactions Per Second |
每秒事务数 |
越高越好 |
| 平均响应时间 |
Mean Latency |
请求平均耗时 |
越低越好 |
| 最大响应时间 |
Max Latency |
最慢请求耗时 |
越低越好 |
| 最小响应时间 |
Min Latency |
最快请求耗时 |
参考值 |
| P90 响应时间 |
90th Percentile |
90% 请求耗时上限 |
越低越好 |
| P99 响应时间 |
99th Percentile |
99% 请求耗时上限 |
越低越好 |
| 错误率 |
Error Rate |
失败请求占比 |
越低越好 |
常用压力测试工具对比
| 工具 |
运行环境 |
协议支持 |
特点 |
适用场景 |
| loadtest |
Node.js |
HTTP/WebSocket |
易用、API 友好 |
Node.js 项目首选 |
| Apache Bench (ab) |
系统自带 |
HTTP |
简单快速 |
快速基准测试 |
| JMeter |
Java GUI |
多协议 |
功能全面、可视化 |
复杂场景测试 |
| autocannon |
Node.js |
HTTP |
高性能、实时输出 |
高并发压测 |
| Artillery |
Node.js |
HTTP/WebSocket |
场景编排 |
业务流程测试 |
loadtest 工具详解
安装
1 2 3 4 5
| npm install -g loadtest
npm install --save-dev loadtest
|
基础用法
1 2 3 4 5 6 7 8
| loadtest http://localhost:3000/api -c 10 --rps 10
loadtest http://localhost:3000/api -t 20 -c 10
loadtest http://localhost:3000/api -t 20 -c 10 --rps
|
常用参数说明
| 参数 |
简写 |
说明 |
示例 |
--concurrency |
-c |
并发客户端数 |
-c 10 |
--maxRequests |
-n |
总请求数 |
-n 1000 |
--maxSeconds |
-t |
测试持续时间(秒) |
-t 30 |
--rps |
|
每秒请求数 |
--rps 100 |
--contentType |
-T |
Content-Type 头 |
-T 'application/json' |
--method |
-m |
HTTP 方法 |
-m POST |
--data |
-p |
POST 请求数据 |
--data '{"key":"value"}' |
--headers |
-H |
自定义请求头 |
-H 'Authorization: Bearer token' |
--insecure |
-k |
忽略 HTTPS 证书错误 |
-k |
实战测试案例
GET 接口压力测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| loadtest http://localhost:6001/api/users \ -c 20 \ -t 60 \ --rps 50
{ "totalRequests": 3000, "totalErrors": 0, "totalTimeSeconds": 60.02, "rps": 50, "meanLatencyMs": 45, "maxLatencyMs": 320, "minLatencyMs": 12, "percentiles": { "50": 38, "90": 89, "95": 120, "99": 280 }, "errorCodes": {} }
|
POST 接口压力测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| loadtest http://192.168.1.128:6010/playerLogin.action \ -c 20 \ -t 30 \ -m POST \ -T 'application/x-www-form-urlencoded' \ --data '{ "mCommand":"playerLogin", "mTimestamp":"1348", "mChannelId":"default", "mData":{ "mLoginId":"zh001", "mPlayerId":"bb9e0984-8d1e-4709-ba7a-aabc2aff7290" }, "mDeviceId":"54acebe9bde8fe26252962b699a1aa81a90be34b", "mPlayerId":"bb9e0984-8d1e-4709-ba7a-aabc2aff7290", "mAreaId":"1", "mVersion":"1.0.13" }'
|
逐步加压测试
使用 --rps 参数(不带数值)让 loadtest 自动逐步提升请求速率,测试系统的极限:
1 2 3 4 5 6 7
| loadtest http://localhost:3000/api \ -t 20 \ -c 10 \ --rps
|
WebSocket 压力测试
1 2 3 4 5
| loadtest ws://localhost:3000/socket \ -c 50 \ -t 60 \ --rps 100
|
编写自定义测试脚本
loadtest 提供 JavaScript API,可以编写复杂的测试场景:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| const loadtest = require('loadtest');
const options = { url: 'http://localhost:3000/api/login', maxRequests: 1000, concurrency: 10, method: 'POST', headers: { 'Content-Type': 'application/json' }, requestGenerator: (params, options, client, callback) => { const userId = Math.floor(Math.random() * 10000); const postData = JSON.stringify({ username: `user_${userId}`, password: 'test123' }); options.headers['Content-Length'] = postData.length; const request = client.request(options, callback); request.write(postData); return request; } };
loadtest.loadTest(options, (error, results) => { if (error) { console.error('测试失败:', error); return; }
console.log('===== 压力测试结果 ====='); console.log(`总请求数: ${results.totalRequests}`); console.log(`总错误数: ${results.totalErrors}`); console.log(`平均 TPS: ${results.rps}`); console.log(`平均响应时间: ${results.meanLatencyMs}ms`); console.log(`最大响应时间: ${results.maxLatencyMs}ms`); console.log(`P90 响应时间: ${results.percentiles['90']}ms`); console.log(`P99 响应时间: ${results.percentiles['99']}ms`);
if (results.meanLatencyMs < 100 && results.totalErrors === 0) { console.log('✅ 系统性能良好'); } else if (results.meanLatencyMs < 500) { console.log('⚠️ 系统性能一般,建议优化'); } else { console.log('❌ 系统性能不足,需要立即优化'); } });
|
运行脚本:
结果解读与性能判定
结果示例分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| { "totalRequests": 49, "totalErrors": 0, "totalTimeSeconds": 5.00, "rps": 10, "meanLatencyMs": 317, "maxLatencyMs": 610, "minLatencyMs": 146, "percentiles": { "50": 300, "90": 474, "95": 550, "99": 610 }, "errorCodes": {} }
|
| 指标 |
数值 |
解读 |
| TPS |
10 |
系统当前处理 10 请求/秒 |
| 平均响应时间 |
317ms |
响应较慢,一般需 <200ms |
| P90 |
474ms |
90% 请求在 474ms 内完成 |
| P99 |
610ms |
1% 请求超过 610ms,存在长尾延迟 |
| 错误数 |
0 |
无失败请求,稳定性良好 |
性能判定标准
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| ┌─────────────────────────────────────────────────────────────────────┐ │ API 接口性能判定标准 │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ 响应时间(RT) │ │ ───────────────── │ │ • P90 < 100ms :优秀 ✅ │ │ • P90 < 200ms :良好 ✅ │ │ • P90 < 500ms :一般 ⚠️ │ │ • P90 > 500ms :需优化 ❌ │ │ │ │ 错误率 │ │ ─────── │ │ • 0% :优秀 ✅ │ │ • < 0.1% :可接受 ⚠️ │ │ • > 0.1% :需排查 ❌ │ │ │ │ TPS 达标率 │ │ ───────── │ │ • 达到目标 TPS 且无错误 :通过 ✅ │ │ • 达到目标 TPS 但有错误 :不稳定 ⚠️ │ │ • 未达到目标 TPS :需优化 ❌ │ │ │ └─────────────────────────────────────────────────────────────────────┘
|
性能优化方向
当压力测试发现问题后,常见的优化方向:
| 问题现象 |
可能原因 |
优化方案 |
| 响应时间高、CPU 低 |
数据库查询慢 |
添加索引、优化 SQL、引入缓存 |
| CPU 100%、响应慢 |
计算密集型操作 |
使用集群模式、优化算法 |
| 内存持续增长 |
内存泄漏 |
使用 heapdump 分析、修复泄漏点 |
| 并发上去后错误增多 |
连接池耗尽 |
增加连接池大小、使用连接池复用 |
| 长尾延迟明显 |
GC 暂停 |
优化 GC 参数、减少大对象分配 |
综合压测流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| ┌─────────────────────────────────────────────────────────────────────┐ │ 压力测试标准流程 │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ 1. 基线测试 │ │ └── 单用户请求,记录响应时间作为基准 │ │ │ │ 2. 容量测试 │ │ └── 逐步增加并发,找到 TPS 峰值点 │ │ │ │ 3. 稳定性测试 │ │ └── 以 80% 峰值负载持续运行 10 分钟以上 │ │ │ │ 4. 极限测试 │ │ └── 继续加压直到系统崩溃,记录崩溃临界点 │ │ │ │ 5. 恢复测试 │ │ └── 停止压测后,观察系统是否恢复正常 │ │ │ └─────────────────────────────────────────────────────────────────────┘
|
总结
Node.js 压力测试的一些经验:
- 指标选择:重点关注 TPS、P90/P95/P99 响应时间和错误率,平均响应时间容易被极端值拉偏
- 工具选择:loadtest 对 Node.js 项目来说比较友好,API 简单,支持 WebSocket
- 逐步加压:从低并发开始逐步提升,找到系统的性能拐点和崩溃临界点
- 场景覆盖:GET 和 POST 接口都要测试,POST 注意 Content-Type 和数据格式
- 结果解读:P99 反映最坏情况下的用户体验,P90 反映绝大多数用户的体验
- 持续测试:性能优化前后都要压测对比,用数据验证优化效果
系统化的压力测试能在上线前发现性能瓶颈,确保系统在生产环境中稳定运行。