10.5.4 性能瓶颈在哪——性能分析:响应时间与资源使用监控
用户说"慢",你要能说出具体慢在哪。
性能指标
用户体验指标
| 指标 | 说明 | 目标值 |
|---|---|---|
| TTFB | 首字节时间 | < 200ms |
| FCP | 首次内容绘制 | < 1.8s |
| LCP | 最大内容绘制 | < 2.5s |
| TTI | 可交互时间 | < 3.8s |
服务端指标
| 指标 | 说明 | 目标值 |
|---|---|---|
| 响应时间 | API 平均响应时间 | < 200ms |
| P95 延迟 | 95% 请求的最大延迟 | < 500ms |
| P99 延迟 | 99% 请求的最大延迟 | < 1s |
| 吞吐量 | 每秒处理请求数 | 根据业务 |
响应时间监控
请求计时中间件
typescript
// NestJS 中间件
@Injectable()
export class TimingMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
console.log(JSON.stringify({
level: 'info',
event: 'request_completed',
method: req.method,
path: req.path,
status: res.statusCode,
duration,
slow: duration > 1000, // 标记慢请求
}));
// 慢请求告警
if (duration > 3000) {
this.alertSlowRequest(req.path, duration);
}
});
next();
}
}数据库查询监控
typescript
// Prisma 查询日志
const prisma = new PrismaClient({
log: [
{ emit: 'event', level: 'query' },
],
});
prisma.$on('query', (e) => {
if (e.duration > 100) { // 慢查询阈值 100ms
console.log(JSON.stringify({
level: 'warn',
event: 'slow_query',
query: e.query,
params: e.params,
duration: e.duration,
}));
}
});资源使用监控
内存监控
typescript
// 定期记录内存使用
setInterval(() => {
const usage = process.memoryUsage();
console.log(JSON.stringify({
level: 'info',
event: 'memory_usage',
heapUsed: Math.round(usage.heapUsed / 1024 / 1024), // MB
heapTotal: Math.round(usage.heapTotal / 1024 / 1024),
rss: Math.round(usage.rss / 1024 / 1024),
}));
// 内存泄漏警告
if (usage.heapUsed > 500 * 1024 * 1024) { // > 500MB
console.warn('High memory usage detected');
}
}, 60000); // 每分钟CPU 监控
typescript
import * as os from 'os';
function getCpuUsage() {
const cpus = os.cpus();
let totalIdle = 0, totalTick = 0;
cpus.forEach(cpu => {
for (const type in cpu.times) {
totalTick += cpu.times[type];
}
totalIdle += cpu.times.idle;
});
return 1 - totalIdle / totalTick;
}性能分析工具
Node.js 内置工具
bash
# 启动时开启性能分析
node --prof app.js
# 生成可读报告
node --prof-process isolate-*.log > profile.txtChrome DevTools
typescript
// 在开发环境启用调试
if (process.env.NODE_ENV === 'development') {
require('inspector').open(9229, 'localhost', true);
}火焰图
bash
# 使用 0x 生成火焰图
npx 0x app.js
# 打开生成的 HTML 文件查看火焰图常见性能问题
N+1 查询问题
typescript
// 问题代码 - N+1 查询
const users = await prisma.user.findMany();
for (const user of users) {
const orders = await prisma.order.findMany({
where: { userId: user.id }
});
}
// 优化后 - 使用 include
const users = await prisma.user.findMany({
include: { orders: true }
});内存泄漏
typescript
// 问题代码 - 事件监听器泄漏
class Service {
constructor() {
eventEmitter.on('data', this.handleData); // 每次实例化都添加
}
}
// 优化后 - 清理监听器
class Service {
constructor() {
eventEmitter.on('data', this.handleData);
}
destroy() {
eventEmitter.off('data', this.handleData);
}
}同步阻塞
typescript
// 问题代码 - 同步文件操作
const data = fs.readFileSync('large-file.txt');
// 优化后 - 异步操作
const data = await fs.promises.readFile('large-file.txt');性能优化清单
数据库层
- [ ] 添加必要的索引
- [ ] 使用
include代替多次查询 - [ ] 分页查询大数据集
- [ ] 使用 Redis 缓存热点数据
应用层
- [ ] 启用 Gzip 压缩
- [ ] 使用连接池
- [ ] 避免同步操作
- [ ] 优化大循环
网络层
- [ ] 使用 CDN
- [ ] 启用 HTTP/2
- [ ] 合理设置缓存头
- [ ] 图片压缩/WebP
压力测试
使用 k6
javascript
// load-test.js
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
vus: 100, // 100 个虚拟用户
duration: '30s', // 持续 30 秒
};
export default function() {
const res = http.get('http://localhost:3000/api/health');
check(res, {
'status is 200': (r) => r.status === 200,
'response time < 200ms': (r) => r.timings.duration < 200,
});
sleep(1);
}bash
# 运行测试
k6 run load-test.js使用 wrk
bash
# 简单压测
wrk -t12 -c400 -d30s http://localhost:3000/api/health
# 输出示例
Running 30s test @ http://localhost:3000/api/health
12 threads and 400 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 50ms 10ms 200ms 85%
Req/Sec 500 50 600 90%
Requests/sec: 6000性能基准
| 规模 | 预期 QPS | 响应时间 |
|---|---|---|
| 小型项目 | 100-500 | < 200ms |
| 中型项目 | 500-2000 | < 100ms |
| 大型项目 | 2000+ | < 50ms |
常见问题
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| 响应时间波动大 | GC 暂停/慢查询 | 优化内存使用/添加索引 |
| CPU 持续高 | 密集计算/死循环 | 火焰图分析定位 |
| 内存持续增长 | 内存泄漏 | 堆快照分析 |
| 偶发超时 | 连接池耗尽 | 增加连接池大小 |
