您现在的位置是:网站首页 > 负载测试文章详情
负载测试
陈川
【
Node.js
】
35510人已围观
7074字
负载测试的基本概念
负载测试是一种性能测试方法,用于评估系统在特定负载条件下的表现。通过模拟真实用户行为,可以测量系统的响应时间、吞吐量、资源利用率等关键指标。在Node.js应用中,负载测试尤为重要,因为其单线程事件循环架构对高并发场景的处理方式与传统多线程服务器不同。
典型的负载测试场景包括:
- 模拟100个并发用户持续访问API端点
- 逐步增加请求频率直到系统达到性能瓶颈
- 长时间运行测试以检测内存泄漏问题
Node.js负载测试工具选择
Node.js生态中有多个成熟的负载测试工具可供选择:
Artillery:
// artillery.yml 配置示例
config:
target: "https://api.example.com"
phases:
- duration: 60
arrivalRate: 10
name: "Warm up"
- duration: 300
arrivalRate: 50
rampTo: 200
name: "Ramp up load"
scenarios:
- flow:
- get:
url: "/products"
- post:
url: "/checkout"
json:
productId: "123"
quantity: 1
k6:
import http from 'k6/http';
import { check, sleep } from 'k6';
export let options = {
stages: [
{ duration: '30s', target: 20 },
{ duration: '1m', target: 100 },
{ duration: '20s', target: 0 },
],
};
export default function () {
let res = http.get('https://test-api.k6.io/public/crocodiles/');
check(res, {
'status is 200': (r) => r.status === 200,
'response time < 500ms': (r) => r.timings.duration < 500,
});
sleep(1);
}
Autocannon:
npx autocannon -c 100 -d 60 -p 10 https://api.example.com/endpoint
测试指标解读与分析
进行负载测试时需要重点关注以下核心指标:
-
响应时间:
- 平均响应时间
- 第95百分位响应时间
- 最大响应时间
-
吞吐量:
- 请求速率(RPS)
- 数据传输量
-
错误率:
- HTTP错误状态码比例
- 超时请求比例
-
资源利用率:
- CPU使用率
- 内存占用
- 事件循环延迟
// 使用Node.js性能钩子监控事件循环延迟
const { monitorEventLoopDelay } = require('perf_hooks');
const h = monitorEventLoopDelay({ resolution: 20 });
h.enable();
setInterval(() => {
console.log({
min: h.min,
max: h.max,
mean: h.mean,
stddev: h.stddev,
percentiles: {
p50: h.percentile(50),
p99: h.percentile(99)
}
});
h.reset();
}, 1000);
常见性能瓶颈与优化
数据库查询优化
// 低效查询示例
app.get('/users', async (req, res) => {
const users = await User.find({});
res.json(users);
});
// 优化后的查询
app.get('/users', async (req, res) => {
const users = await User.find({})
.select('name email') // 只选择必要字段
.limit(100) // 限制返回数量
.lean(); // 返回普通JS对象而非Mongoose文档
res.json(users);
});
连接池配置
// MySQL连接池配置示例
const mysql = require('mysql2/promise');
const pool = mysql.createPool({
host: 'localhost',
user: 'root',
database: 'test',
waitForConnections: true,
connectionLimit: 50, // 重要参数
queueLimit: 0,
enableKeepAlive: true,
keepAliveInitialDelay: 0
});
// 使用连接池
app.get('/data', async (req, res) => {
const [rows] = await pool.query('SELECT * FROM large_table');
res.json(rows);
});
缓存策略实现
// Redis缓存示例
const redis = require('redis');
const { promisify } = require('util');
const client = redis.createClient();
const getAsync = promisify(client.get).bind(client);
const setexAsync = promisify(client.setex).bind(client);
app.get('/products/:id', async (req, res) => {
const cacheKey = `product_${req.params.id}`;
let product = await getAsync(cacheKey);
if (!product) {
product = await Product.findById(req.params.id);
await setexAsync(cacheKey, 3600, JSON.stringify(product)); // 缓存1小时
} else {
product = JSON.parse(product);
}
res.json(product);
});
高级测试场景设计
混合负载测试
// 模拟不同用户行为的测试脚本
import http from 'k6';
import { randomIntBetween } from 'https://jslib.k6.io/k6-utils/1.2.0/index.js';
export let options = {
scenarios: {
browse_products: {
executor: 'constant-vus',
exec: 'browse',
vus: 50,
duration: '5m',
},
checkout: {
executor: 'ramping-vus',
exec: 'checkout',
startVUs: 5,
stages: [
{ duration: '2m', target: 20 },
{ duration: '3m', target: 20 },
],
},
},
};
export function browse() {
http.get(`https://shop.example.com/products/${randomIntBetween(1,100)}`);
}
export function checkout() {
let res = http.post('https://shop.example.com/cart', {
productId: randomIntBetween(1,100),
quantity: randomIntBetween(1,3)
});
}
分布式压力测试
# 使用artillery运行分布式测试
artillery run --config config.yml --target "https://api.example.com" scenario.yml \
--shard-1-of-4 & \
artillery run --config config.yml --target "https://api.example.com" scenario.yml \
--shard-2-of-4 & \
artillery run --config config.yml --target "https://api.example.com" scenario.yml \
--shard-3-of-4 & \
artillery run --config config.yml --target "https://api.example.com" scenario.yml \
--shard-4-of-4
监控与可视化
实时监控仪表板
// 使用Prometheus和Grafana监控Node.js应用
const client = require('prom-client');
// 定义指标
const httpRequestDurationMicroseconds = new client.Histogram({
name: 'http_request_duration_ms',
help: 'Duration of HTTP requests in ms',
labelNames: ['method', 'route', 'code'],
buckets: [0.1, 5, 15, 50, 100, 200, 300, 400, 500]
});
// 中间件记录请求时间
app.use((req, res, next) => {
const end = httpRequestDurationMicroseconds.startTimer();
res.on('finish', () => {
end({
method: req.method,
route: req.route.path,
code: res.statusCode
});
});
next();
});
// 暴露指标端点
app.get('/metrics', async (req, res) => {
res.set('Content-Type', client.register.contentType);
res.end(await client.register.metrics());
});
日志聚合分析
// 结构化日志配置
const { createLogger, transports, format } = require('winston');
const logger = createLogger({
level: 'info',
format: format.combine(
format.timestamp(),
format.json()
),
transports: [
new transports.File({ filename: 'combined.log' }),
new transports.Console({
format: format.combine(
format.colorize(),
format.simple()
)
})
]
});
// 在路由中使用
app.post('/order', async (req, res) => {
const start = Date.now();
try {
const order = await createOrder(req.body);
logger.info('Order created', {
orderId: order.id,
duration: Date.now() - start,
userId: req.user.id
});
res.json(order);
} catch (err) {
logger.error('Order failed', {
error: err.message,
duration: Date.now() - start,
userId: req.user.id
});
res.status(500).json({ error: err.message });
}
});
持续集成中的负载测试
# GitHub Actions 配置示例
name: Load Test
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
load-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Node.js
uses: actions/setup-node@v2
with:
node-version: '16'
- run: npm install -g artillery
- run: artillery run --output report.json test/load-test.yml
- name: Upload report
uses: actions/upload-artifact@v2
with:
name: load-test-report
path: report.json
- name: Fail on performance regression
run: |
if grep -q 'median: [5-9][0-9][0-9]' report.json; then
echo "Performance regression detected"
exit 1
fi