Node.js 压力测试实战:loadtest 工具使用与性能指标解读

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
# 基础测试:10 个并发客户端,每秒总共发送 10 个请求
loadtest http://localhost:3000/api -c 10 --rps 10

# 带持续时间的测试:20 秒,10 个并发
loadtest http://localhost:3000/api -t 20 -c 10

# 逐步提升 RPS 测试系统极限
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
# 测试用户登录接口(POST + JSON 数据)
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

# loadtest 会自动逐步提升每个客户端的 RPS
# 直到出现大量错误或响应时间显著增长

WebSocket 压力测试

1
2
3
4
5
# 测试 WebSocket 接口
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
// loadtest-script.js
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
node loadtest-script.js

结果解读与性能判定

结果示例分析

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 压力测试的一些经验:

  1. 指标选择:重点关注 TPS、P90/P95/P99 响应时间和错误率,平均响应时间容易被极端值拉偏
  2. 工具选择:loadtest 对 Node.js 项目来说比较友好,API 简单,支持 WebSocket
  3. 逐步加压:从低并发开始逐步提升,找到系统的性能拐点和崩溃临界点
  4. 场景覆盖:GET 和 POST 接口都要测试,POST 注意 Content-Type 和数据格式
  5. 结果解读:P99 反映最坏情况下的用户体验,P90 反映绝大多数用户的体验
  6. 持续测试:性能优化前后都要压测对比,用数据验证优化效果

系统化的压力测试能在上线前发现性能瓶颈,确保系统在生产环境中稳定运行。