Node.js 性能优化实战:从基础到进阶

5 阅读2分钟

Node.js 性能优化实战:从基础到进阶

引言

Node.js 以其高性能和异步 I/O 特性而闻名,但在实际应用中,如果不注意性能优化,仍然会遇到各种性能问题。本文将从基础到进阶,系统地介绍 Node.js 性能优化的各种技巧和最佳实践。

一、事件循环优化

1.1 避免阻塞事件循环

Node.js 是单线程模型,任何阻塞操作都会影响整个应用的性能。

// 错误示例:同步文件读取
const fs = require('fs');
const data = fs.readFileSync('/path/to/file');  // 阻塞!

// 正确做法:异步读取
fs.readFile('/path/to/file', (err, data) => {
  if (err) throw err;
  console.log(data);
});

// 更好的做法:使用 Promise
const fsPromises = require('fs').promises;

async function readFileAsync() {
  try {
    const data = await fsPromises.readFile('/path/to/file');
    console.log(data);
  } catch (err) {
    console.error(err);
  }
}

1.2 合理使用 setImmediate 和 process.nextTick

// setImmediate: 在 I/O 事件之后执行
setImmediate(() => {
  console.log('在 I/O 之后执行');
});

// process.nextTick: 在当前操作完成后立即执行
process.nextTick(() => {
  console.log('立即执行');
});

二、内存管理

2.1 避免内存泄漏

常见的内存泄漏场景:

// 1. 全局变量累积
const cache = {};
function addToCache(key, value) {
  cache[key] = value;  // 没有清理机制,会一直增长
}

// 解决方案:使用 LRU 缓存
const LRU = require('lru-cache');
const cache = new LRU({
  max: 500,
  maxAge: 1000 * 60 * 60  // 1小时
});

// 2. 事件监听器没有移除
const EventEmitter = require('events');
const emitter = new EventEmitter();

function setupListener() {
  emitter.on('event', handler);  // 如果不移除,会一直占用内存
}

// 正确做法
function cleanup() {
  emitter.removeListener('event', handler);
}

2.2 使用 Stream 处理大文件

// 错误:一次性读取整个文件
const fs = require('fs');
fs.readFile('large-file.txt', (err, data) => {
  // 大文件会占用大量内存
});

// 正确:使用 Stream
const readStream = fs.createReadStream('large-file.txt');
const writeStream = fs.createWriteStream('output.txt');

readStream.pipe(writeStream);

readStream.on('data', (chunk) => {
  console.log(`读取 ${chunk.length} 字节`);
});

三、数据库优化

3.1 连接池管理

const mysql = require('mysql2/promise');

// 创建连接池
const pool = mysql.createPool({
  host: 'localhost',
  user: 'root',
  database: 'test',
  waitForConnections: true,
  connectionLimit: 10,
  queueLimit: 0
});

// 使用连接池
async function query(sql) {
  const [rows] = await pool.execute(sql);
  return rows;
}

3.2 查询优化

// 使用索引
// CREATE INDEX idx_user_email ON users(email);

// 批量插入
const values = [
  [1, 'user1'],
  [2, 'user2'],
  [3, 'user3']
];

await pool.query(
  'INSERT INTO users (id, name) VALUES ?',
  [values]
);

// 使用 SELECT 指定字段,而不是 SELECT *
const [users] = await pool.execute(
  'SELECT id, name, email FROM users WHERE status = ?',
  ['active']
);

四、缓存策略

4.1 内存缓存

const NodeCache = require('node-cache');
const cache = new NodeCache({ stdTTL: 600 });

async function getUserData(userId) {
  // 先查缓存
  const cached = cache.get(`user_${userId}`);
  if (cached) {
    return cached;
  }
  
  // 缓存未命中,查数据库
  const user = await db.query('SELECT * FROM users WHERE id = ?', [userId]);
  
  // 存入缓存
  cache.set(`user_${userId}`, user);
  
  return user;
}

4.2 Redis 缓存

const redis = require('redis');
const client = redis.createClient();

async function cacheData(key, data, ttl = 3600) {
  await client.setEx(key, ttl, JSON.stringify(data));
}

async function getCachedData(key) {
  const data = await client.get(key);
  return data ? JSON.parse(data) : null;
}

五、并发控制

5.1 使用 Worker Threads

const { Worker } = require('worker_threads');

function runWorker(workerData) {
  return new Promise((resolve, reject) => {
    const worker = new Worker('./worker.js', { workerData });
    
    worker.on('message', resolve);
    worker.on('error', reject);
    worker.on('exit', (code) => {
      if (code !== 0) {
        reject(new Error(`Worker stopped with exit code ${code}`));
      }
    });
  });
}

// 使用
async function processData(data) {
  const result = await runWorker(data);
  return result;
}

5.2 限制并发数

const pLimit = require('p-limit');
const limit = pLimit(5);  // 最多 5 个并发

const tasks = urls.map(url => 
  limit(() => fetch(url))
);

const results = await Promise.all(tasks);

六、监控和调试

6.1 使用 Clinic.js

# 安装
npm install -g clinic

# 运行诊断
clinic doctor -- node app.js
clinic flame -- node app.js
clinic bubbleprof -- node app.js

6.2 性能监控

const { performance } = require('perf_hooks');

function measurePerformance(fn, label) {
  const start = performance.now();
  const result = fn();
  const end = performance.now();
  
  console.log(`${label}: ${end - start}ms`);
  return result;
}

// 使用
measurePerformance(() => {
  // 你的代码
}, '操作耗时');

总结

Node.js 性能优化是一个系统工程,需要从多个方面入手:

  1. 事件循环优化:避免阻塞操作
  2. 内存管理:防止内存泄漏,合理使用 Stream
  3. 数据库优化:使用连接池和查询优化
  4. 缓存策略:合理使用内存和 Redis 缓存
  5. 并发控制:使用 Worker Threads 和限制并发数
  6. 监控调试:使用专业工具进行性能分析

通过这些优化手段,可以显著提升 Node.js 应用的性能和稳定性。