性能测试

以下是基于 Node.js 的性能测试完整示例,演示如何对一个 REST API 进行压力测试,分析响应时间、吞吐量和资源消耗,并定位性能瓶颈。


1. 场景描述

假设有一个用户查询接口 GET /api/users/:id,需要测试其在以下场景下的表现:

  • 高并发请求:模拟 1000 个用户每秒(RPS)的访问量。
  • 长时稳定性:持续运行 5 分钟,观察内存泄漏和错误率。
  • 依赖分析:接口依赖 MongoDB 数据库和 Redis 缓存。

2. 工具选择

工具 作用
k6 开源性能测试工具,支持脚本化和云原生场景,适合高并发压测。
autocannon 轻量级 HTTP 压测工具,适合快速本地测试。
PM2 进程管理工具,监控 Node.js 应用的 CPU/内存使用。
MongoDB Atlas 托管数据库服务,提供性能监控指标(如查询延迟、连接数)。
Docker 隔离测试环境,确保压测不影响本地开发环境。

3. 性能测试实现步骤

步骤 1:准备待测试的 API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// src/app.js
const express = require("express");
const { getDb } = require("./db");
const app = express();

// 用户查询接口
app.get("/api/users/:id", async (req, res) => {
try {
const user = await getDb().collection("users").findOne({ _id: req.params.id });
res.json(user || { error: "User not found" });
} catch (error) {
res.status(500).json({ error: "Database error" });
}
});

const server = app.listen(3000, () => console.log("Server running on port 3000"));
module.exports = server;

步骤 2:编写 k6 性能测试脚本

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
// tests/performance/user-load-test.js
import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
stages: [
{ duration: '30s', target: 500 }, // 30秒内逐步增加到500并发用户
{ duration: '2m', target: 1000 }, // 维持1000并发用户2分钟
{ duration: '30s', target: 0 }, // 逐步停止
],
thresholds: {
http_req_duration: ['p(95)<500'], // 95%的请求响应时间需小于500ms
http_req_failed: ['rate<0.01'], // 错误率低于1%
},
};

export default function () {
const userId = '507f191e810c19729de860ea'; // 测试用户ID
const res = http.get(`http://localhost:3000/api/users/${userId}`);

check(res, {
'Status is 200': (r) => r.status === 200,
'Has valid user data': (r) => JSON.parse(r.body).name !== undefined,
});

sleep(1); // 每个用户每秒执行1次请求(RPS = VU * 1)
}

步骤 3:运行测试并监控资源

1
2
3
4
5
6
7
8
# 启动服务(通过 PM2 监控资源)
pm2 start src/app.js --name "user-api"

# 运行 k6 压测(1000 并发用户)
k6 run tests/performance/user-load-test.js

# 实时监控资源
pm2 monit

4. 性能测试结果分析

(1) k6 输出报告

1
2
3
4
5
6
7
✓ Status is 200
✓ Has valid user data

checks.....................: 99.8% ✓ 89910 ✗ 180
http_req_duration.........: avg=220ms min=50ms med=180ms max=1.2s p(95)=450ms
http_reqs..................: 90090 (≈ 1001.0 RPS)
iteration_duration.........: avg=1.22s min=1.01s med=1.18s max=2.5s
  • 关键指标
    • 吞吐量(RPS):1001 请求/秒。
    • 95% 响应时间:450ms。
    • 错误率:0.2%(180次失败)。

(2) PM2 资源监控

1
2
CPU Usage: 85% (~90% during peak)
Memory Usage: 512MB (稳定,无泄漏)

(3) MongoDB Atlas 监控

  • 查询延迟:平均 150ms,峰值 300ms。
  • 连接数:峰值 200 个连接。

5. 性能瓶颈定位与优化

瓶颈 1:数据库查询延迟过高

  • 根因:未对 _id 字段建立索引。
  • 优化
    1
    2
    // 在 MongoDB 中创建索引
    db.users.createIndex({ _id: 1 });

瓶颈 2:Node.js CPU 接近满载

  • 根因:同步阻塞操作(如未缓存的 JWT 验证)。
  • 优化
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 使用缓存(Redis)存储常用数据
    app.get("/api/users/:id", async (req, res) => {
    const cachedUser = await redis.get(`user:${req.params.id}`);
    if (cachedUser) return res.json(JSON.parse(cachedUser));

    // 数据库查询并缓存结果
    const user = await db.collection("users").findOne({ _id: req.params.id });
    await redis.set(`user:${req.params.id}`, JSON.stringify(user), 'EX', 60);
    res.json(user);
    });

瓶颈 3:HTTP 连接池不足

  • 根因:Node.js 默认的 http.globalAgent 限制连接数。
  • 优化
    1
    2
    3
    // 调整全局 Agent 参数
    const http = require('http');
    http.globalAgent.maxSockets = 1000; // 提高并发连接数

6. 优化后复测结果

1
2
3
http_req_duration.........: avg=80ms   min=30ms   med=70ms  max=300ms p(95)=120ms
http_reqs..................: 90090 (≈ 2500 RPS)
CPU Usage.................: 45% (下降约50%)

7. 工具链扩展

场景 工具 用途
分布式压测 k6 Cloud 模拟全球用户分布,生成可视化报告。
内存泄漏检测 Node.js Inspector + Chrome DevTools 分析堆内存快照。
日志分析 ELK Stack 聚合压测期间的错误日志,定位异常。
全链路监控 Prometheus + Grafana 监控微服务链路中各组件(API、DB、缓存)。

总结

通过性能测试,可以:

  1. 量化系统能力:确定服务的最大吞吐量和临界负载。
  2. 暴露瓶颈:识别代码、数据库或基础设施的性能短板。
  3. 验证优化效果:通过对比测试数据,确认优化措施的有效性。

最佳实践

  • 基准测试(Baseline):在每次重大变更后运行性能测试,建立历史数据对比。
  • 自动化流水线:将性能测试集成到 CI/CD,设置阈值拦截性能退化。
  • 生产环境影子测试:通过流量复制(如 GoReplay),在生产环境安全压测。

以下是 k6PM2 的安装方法,支持 macOS、Linux 和 Windows 系统:


1. 安装 k6(性能测试工具)

k6 支持多种安装方式,根据操作系统选择以下任一方法:

macOS

1
2
# 使用 Homebrew 安装
brew install k6

Linux (Debian/Ubuntu)

1
2
3
4
5
6
# 添加 k6 官方仓库
sudo gpg -k
sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list
sudo apt-get update
sudo apt-get install k6

Windows

  1. 访问 k6 官网下载页面
  2. 下载 .msi 安装包并双击运行。
  3. 安装完成后,打开 PowerShell 验证:
    1
    k6 version

或者 choco install k6

验证安装

1
2
k6 version
# 输出示例:k6 v0.45.0 (2023-XX-XX)

2. 安装 PM2(Node.js 进程管理工具)

PM2 是一个 Node.js 进程管理器,需通过 npm 全局安装:

所有操作系统

1
2
3
4
5
6
# 安装 PM2
npm install pm2@latest -g

# 验证安装
pm2 --version
# 输出示例:5.3.0

权限问题处理(Linux/macOS)

如果安装时提示权限不足,可使用 sudo 或修复 npm 目录权限:

1
2
3
4
5
6
7
8
9
# 使用 sudo(不推荐长期使用)
sudo npm install pm2@latest -g

# 或修复 npm 目录权限
mkdir ~/.npm-global
npm config set prefix '~/.npm-global'
echo 'export PATH=~/.npm-global/bin:$PATH' >> ~/.bashrc
source ~/.bashrc
npm install pm2@latest -g

3. 常见问题

k6 安装失败

  • 网络问题:尝试使用镜像源(如国内用户):
    1
    2
    3
    # 临时替换 Homebrew 镜像源(macOS/Linux)
    export HOMEBREW_BOTTLE_DOMAIN=https://mirrors.ustc.edu.cn/homebrew-bottles
    brew install k6
  • 手动下载:从 k6 Releases 下载二进制文件,解压后添加到 PATH

PM2 命令未找到

  • 环境变量问题:确保 npm 全局目录(如 ~/.npm-global/bin)已添加到 PATH
    1
    2
    echo 'export PATH=$PATH:~/.npm-global/bin' >> ~/.bashrc
    source ~/.bashrc

4. 使用示例

启动 Node.js 服务并监控

1
2
pm2 start app.js --name "my-api" --watch
pm2 monit # 实时监控 CPU/内存

运行 k6 性能测试

1
k6 run test-script.js

通过以上步骤,可快速安装并验证 k6 和 PM2 的可用性。