DevToolBox免费
博客

Node.js性能优化:集群、流、性能分析和缓存

12分钟作者 DevToolBox

Node.js 在单台服务器上可处理数万个并发连接——但前提是避免常见的性能陷阱。本指南涵盖利用所有 CPU 核心的集群、避免内存溢出的、查找瓶颈的性能分析,以及可将响应时间减少 90% 的缓存策略

理解事件循环

Node.js 是单线程的。阻塞事件循环会阻塞所有请求。最关键的性能规则:永远不要在主线程中同步运行 CPU 密集型操作。

// NEVER do this — blocks the event loop
app.get('/compute', (req, res) => {
  // Synchronous CPU-heavy computation blocks ALL requests
  let result = 0;
  for (let i = 0; i < 1e9; i++) result += i;  // 1 billion iterations!
  res.json({ result });
});

// DO THIS instead — offload to worker thread
const { Worker, isMainThread, parentPort, workerData } = require('worker_threads');

app.get('/compute', (req, res) => {
  const worker = new Worker('./computeWorker.js', {
    workerData: { input: req.query.n }
  });
  worker.on('message', result => res.json({ result }));
  worker.on('error', err => res.status(500).json({ error: err.message }));
});

集群实现多核性能

Node.js 默认只运行在单个 CPU 核上。cluster 模块可创建共享服务器端口的子进程,利用所有可用 CPU 核。

// Node.js Cluster Module — Use All CPU Cores
const cluster = require('cluster');
const os = require('os');
const express = require('express');

const NUM_WORKERS = os.cpus().length;

if (cluster.isPrimary) {
  console.log(`Primary ${process.pid} is running`);
  console.log(`Starting ${NUM_WORKERS} workers...`);

  // Fork workers
  for (let i = 0; i < NUM_WORKERS; i++) {
    cluster.fork();
  }

  cluster.on('exit', (worker, code, signal) => {
    console.log(`Worker ${worker.process.pid} died (${signal || code}). Restarting...`);
    cluster.fork(); // Auto-restart crashed workers
  });

  cluster.on('online', (worker) => {
    console.log(`Worker ${worker.process.pid} is online`);
  });

} else {
  // Worker process — runs the actual server
  const app = express();

  app.get('/api/users', async (req, res) => {
    const users = await db.getUsers();
    res.json(users);
  });

  app.listen(3000, () => {
    console.log(`Worker ${process.pid} listening on port 3000`);
  });
}

// Alternative: PM2 cluster mode (recommended for production)
// pm2 start server.js -i max   # auto-detect CPU count
// pm2 start server.js -i 4     # explicit count

流实现内存效率

流允许逐块处理数据而不将所有内容加载到内存,对文件处理、HTTP 响应和数据库游标至关重要。

// Node.js Streams — Memory-Efficient Processing

const fs = require('fs');
const { Transform, pipeline } = require('stream');
const { promisify } = require('util');
const pipelineAsync = promisify(pipeline);

// 1. Stream a large file as HTTP response (no memory buffering)
app.get('/download/large-file', (req, res) => {
  const filePath = './large-file.csv';
  const stat = fs.statSync(filePath);

  res.setHeader('Content-Type', 'text/csv');
  res.setHeader('Content-Length', stat.size);
  res.setHeader('Content-Disposition', 'attachment; filename=data.csv');

  // Pipe file directly to response — never fully in memory
  fs.createReadStream(filePath).pipe(res);
});

// 2. Transform stream for CSV processing
class CsvParser extends Transform {
  constructor() {
    super({ objectMode: true });
    this.buffer = '';
    this.headers = null;
  }

  _transform(chunk, encoding, callback) {
    this.buffer += chunk.toString();
    const lines = this.buffer.split('\n');
    this.buffer = lines.pop(); // Keep incomplete line in buffer

    for (const line of lines) {
      if (!this.headers) {
        this.headers = line.split(',');
        continue;
      }
      const values = line.split(',');
      const record = {};
      this.headers.forEach((h, i) => record[h.trim()] = values[i]?.trim());
      this.push(record);
    }
    callback();
  }
}

// 3. Pipeline for reliable error handling
async function processLargeCsvFile(inputPath, outputPath) {
  await pipelineAsync(
    fs.createReadStream(inputPath),
    new CsvParser(),
    new Transform({
      objectMode: true,
      transform(record, enc, cb) {
        // Transform each record
        record.processed = true;
        cb(null, JSON.stringify(record) + '\n');
      }
    }),
    fs.createWriteStream(outputPath)
  );
  console.log('Processing complete');
}

缓存策略

缓存是影响最大的性能优化。即使简单的内存缓存也能将读密集型应用的数据库负载降低 80-90%。

// Caching Strategies for Node.js

// 1. In-Memory LRU Cache
const { LRUCache } = require('lru-cache');

const cache = new LRUCache({
  max: 500,           // Maximum 500 items
  ttl: 5 * 60 * 1000, // 5 minutes TTL
  allowStale: true,   // Return stale value while refreshing
  updateAgeOnGet: true,
});

async function getUser(id) {
  const cacheKey = `user:${id}`;
  const cached = cache.get(cacheKey);
  if (cached) return cached;

  const user = await db.findUser(id);
  cache.set(cacheKey, user);
  return user;
}

// 2. Redis Cache with Stale-While-Revalidate
const Redis = require('ioredis');
const redis = new Redis();

async function getCachedData(key, fetchFn, ttl = 300) {
  const [cached, ttlRemaining] = await redis.pipeline()
    .get(key)
    .ttl(key)
    .exec();

  if (cached[1]) {
    const data = JSON.parse(cached[1]);

    // Background refresh when < 60 seconds remaining
    if (ttlRemaining[1] < 60) {
      fetchFn().then(fresh =>
        redis.setex(key, ttl, JSON.stringify(fresh))
      );
    }

    return data;
  }

  const data = await fetchFn();
  await redis.setex(key, ttl, JSON.stringify(data));
  return data;
}

// 3. HTTP Response Caching with ETags
app.get('/api/products', async (req, res) => {
  const products = await getProducts();
  const etag = require('crypto')
    .createHash('md5')
    .update(JSON.stringify(products))
    .digest('hex');

  if (req.headers['if-none-match'] === etag) {
    return res.status(304).end();
  }

  res.setHeader('ETag', etag);
  res.setHeader('Cache-Control', 'public, max-age=60, stale-while-revalidate=300');
  res.json(products);
});

常见问题

应该创建多少个 worker 线程或集群 worker?

集群(多进程):每个 CPU 核创建一个 worker,即 os.cpus().length 个 worker。线程数过多会导致上下文切换开销。

什么时候使用流而不是将数据加载到内存?

以下情况使用流:处理超过 10MB 的文件、在源之间管道传输数据、增量处理数据、或需要背压控制。

--inspect 标志是什么,如何使用?

--inspect 标志以启用 V8 检查器协议启动 Node.js。在 Chrome 中打开 chrome://inspect 即可将 DevTools 附加到 Node.js 进程。

为什么我的 Node.js 应用使用这么多内存?

常见原因:内存泄漏(未移除的事件监听器、闭包持有引用)、无淘汰策略的缓存、Buffer 误用。

相关工具

𝕏 Twitterin LinkedIn
这篇文章有帮助吗?

保持更新

获取每周开发技巧和新工具通知。

无垃圾邮件,随时退订。

试试这些相关工具

{ }JSON Formatter

相关文章

Node.js Streams 完全指南:Readable、Writable、Transform 与 Pipeline

精通 Node.js 流 — readable、writable、transform 流、pipeline API 和背压处理。

Redis 缓存模式:Web 应用完全指南 (2026)

学习 Redis 缓存模式。

Docker 最佳实践:20 个生产容器技巧

掌握 Docker 的 20 个关键最佳实践:多阶段构建、安全加固、镜像优化、缓存策略、健康检查和 CI/CD 自动化。