用日志驱动 Node.js 负载测试与瓶颈定位
一、整体思路与闭环流程
二、应用埋点与日志规范
// 安装:npm i pino-http express-pino-logger
const express = require('express');
const app = express();
const pino = require('pino')();
const logger = require('pino-http')({
logger,
// 生产建议关闭,压测可临时开启
// quietReqLogger: true,
// customProps: (req, res) => ({ trace_id: req.id }),
});
app.use(logger);
app.get('/ping', (req, res) => {
res.json({ ok: true });
});
// 示例:记录依赖耗时(db/redis/http)
app.get('/slow', async (req, res) => {
const end = res.end.bind(res);
res.end = (...args) => {
const duration = Date.now() - req.startTime;
req.log.info({ duration_ms: duration, status: res.statusCode }, 'request completed');
end(...args);
};
req.startTime = Date.now();
// 伪代码:记录外部依赖
// const dbStart = Date.now(); await db.query(...); const dbDuration = Date.now() - dbStart;
// req.log.debug({ db_duration_ms: dbDuration }, 'db query done');
await new Promise(r => setTimeout(r, 50 + Math.random() * 100));
res.json({ delay: '50-150ms' });
});
app.listen(3000, () => logger.info('server listening on :3000'));
三、执行负载测试与日志增强
ab -c 100 -t 30s http://localhost:3000/pingwrk -t12 -c400 -d30s http://localhost:3000/pingconfig:
target: "http://localhost:3000"
phases:
- duration: 60 # 秒
arrivalRate: 50 # 每秒启动 50 个虚拟用户
- duration: 120
arrivalRate: 100
engines:
socketio: {}
scenarios:
- flow:
- get:
url: "/ping"
- think: 1
运行与报告:
npx artillery run --output report.json scenario.ymlnpx artillery report report.jsonimport http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
stages: [
{ duration: '60s', target: 50 },
{ duration: '120s', target: 100 },
],
thresholds: {
http_req_duration: ['p95<300'],
http_req_failed: ['rate<0.01'],
},
};
export default function () {
const res = http.get('http://localhost:3000/ping');
check(res, { 'status is 200': (r) => r.status === 200 });
sleep(1);
}
四、用日志与监控定位瓶颈
grep 'ms' combined.log | awk '{sum+=$4; c++} END {print "avg=" sum/c}'(按实际日志格式调整字段)。五、优化与持续验证