Appearance
性能问题初步诊断
当应用变慢时,需要系统地排查问题。
内存使用
process.memoryUsage()
javascript
const mem = process.memoryUsage();
console.log({
rss: `${Math.round(mem.rss / 1024 / 1024)} MB`, // 总内存
heapTotal: `${Math.round(mem.heapTotal / 1024 / 1024)} MB`, // 堆总量
heapUsed: `${Math.round(mem.heapUsed / 1024 / 1024)} MB`, // 堆使用
external: `${Math.round(mem.external / 1024 / 1024)} MB` // C++ 对象
});定期监控
javascript
setInterval(() => {
const mem = process.memoryUsage();
console.log(`内存: ${Math.round(mem.heapUsed / 1024 / 1024)} MB`);
}, 5000);识别内存泄漏
javascript
// 可疑的模式
const cache = {};
function process(id, data) {
cache[id] = data; // 只增不减
}
// 改进:限制缓存大小
const cache = new Map();
const MAX_SIZE = 1000;
function process(id, data) {
if (cache.size >= MAX_SIZE) {
const firstKey = cache.keys().next().value;
cache.delete(firstKey);
}
cache.set(id, data);
}时间测量
console.time
javascript
console.time('数据库查询');
const users = await db.query('SELECT * FROM users');
console.timeEnd('数据库查询');
// 数据库查询: 45.123ms高精度计时
javascript
const start = process.hrtime.bigint();
// 执行操作
const end = process.hrtime.bigint();
console.log(`耗时: ${(end - start) / 1000000n} ms`);Performance API
javascript
const { performance } = require('perf_hooks');
const start = performance.now();
// 执行操作
const duration = performance.now() - start;
console.log(`耗时: ${duration.toFixed(2)} ms`);事件循环阻塞
检测阻塞
javascript
let lastCheck = Date.now();
setInterval(() => {
const now = Date.now();
const delay = now - lastCheck - 100; // 100ms 间隔
if (delay > 50) { // 超过 50ms 延迟
console.warn(`事件循环阻塞: ${delay}ms`);
}
lastCheck = now;
}, 100);常见阻塞原因
javascript
// 同步读取大文件
const content = fs.readFileSync('large-file.txt'); // 阻塞
// 改用异步
const content = await fs.promises.readFile('large-file.txt');
// 复杂计算
for (let i = 0; i < 10000000; i++) {
// 大量计算阻塞事件循环
}
// 改用 setImmediate 分片
async function processLarge(items) {
const CHUNK = 1000;
for (let i = 0; i < items.length; i += CHUNK) {
const chunk = items.slice(i, i + CHUNK);
processChunk(chunk);
await new Promise(r => setImmediate(r)); // 让出控制权
}
}CPU 分析
使用 --prof
bash
node --prof app.js运行后生成 isolate-xxx-v8.log。
处理日志:
bash
node --prof-process isolate-xxx-v8.log > profile.txt分析结果
[Summary]:
ticks total nonlib name
123 10.5% 12.3% JavaScript
800 68.2% 80.0% C++
50 4.3% 5.0% GC
200 17.1% Shared libraries
[JavaScript]:
ticks total nonlib name
50 4.3% 5.0% processData
30 2.6% 3.0% parseJSON重点关注高占比的函数。
Clinic.js
可视化性能分析工具:
bash
npm install -g clinicDoctor
bash
clinic doctor -- node app.js
# 访问应用后 Ctrl+C
# 自动打开分析报告Flame
bash
clinic flame -- node app.js
# 生成火焰图常见性能问题
同步操作
javascript
// 慢
const files = fs.readdirSync(dir);
for (const file of files) {
const content = fs.readFileSync(path.join(dir, file));
}
// 快
const files = await fs.promises.readdir(dir);
await Promise.all(files.map(async (file) => {
const content = await fs.promises.readFile(path.join(dir, file));
}));重复计算
javascript
// 慢:每次请求都解析
app.get('/config', (req, res) => {
const config = JSON.parse(fs.readFileSync('config.json'));
res.json(config);
});
// 快:启动时加载一次
const config = JSON.parse(fs.readFileSync('config.json'));
app.get('/config', (req, res) => {
res.json(config);
});无限制并发
javascript
// 危险:可能耗尽资源
await Promise.all(urls.map(url => fetch(url)));
// 安全:限制并发
async function fetchWithLimit(urls, limit = 5) {
const results = [];
let index = 0;
async function worker() {
while (index < urls.length) {
const i = index++;
results[i] = await fetch(urls[i]);
}
}
await Promise.all(Array(limit).fill().map(worker));
return results;
}快速诊断清单
内存问题
- 检查
process.memoryUsage() - 内存是否持续增长?
- 检查
CPU 问题
- 使用
--prof分析 - 是否有同步阻塞操作?
- 使用
I/O 问题
- 测量数据库查询时间
- 外部 API 响应时间
并发问题
- 连接池是否耗尽?
- 是否有资源竞争?
本章小结
process.memoryUsage()监控内存console.time测量代码耗时- 检测事件循环阻塞
--prof分析 CPU 使用- 避免同步操作和无限并发
- 使用 Clinic.js 可视化分析
下一章我们将总结全书内容并展望后续学习路径。