温馨提示×

Node.js在Linux上的并发如何处理

小樊
38
2025-12-26 05:47:58
栏目: 编程语言

Node.js 在 Linux 上的并发处理

并发模型概览

  • Linux 上,Node.js 以 事件驱动 + 非阻塞 I/O 为核心:JavaScript 运行在单线程的 事件循环 中,I/O 操作由底层 libuv 抽象,通过 epoll 等机制高效等待事件,避免线程阻塞。对于文件等无法真正异步的系统调用,libuv 使用 线程池 来模拟异步,从而不占用事件循环线程。为利用多核,Node 提供 多进程/多线程 手段来扩展吞吐。整体适合 I/O 密集型 场景,CPU 密集任务需额外并行化。

Linux 下的并发手段

  • 多进程扩展:使用 cluster 模块创建多个工作进程共享同一端口,主进程负责监控与重启,工作进程各自运行事件循环,适合高并发 Web 服务与多核利用。
  • 子进程并行:使用 child_process(如 spawn/exec)运行外部命令或 Node 脚本,适合与现有程序或脚本解耦、并行化批处理任务。
  • 多线程并行:使用 worker_threads 在单进程内运行多个线程,适合 CPU 密集型 计算;线程间可共享内存(如 ArrayBuffer/SharedArrayBuffer),减少序列化开销。注意:对纯 I/O 密集型 任务,多线程通常不如异步 I/O 高效。
  • 异步编程模型:使用 Promise/async-await 管理大量并发异步任务,结合并发控制(如有限并发、分批)提升稳定性与资源利用率。

如何选择并发策略

场景 首选方案 说明
高并发 HTTP 服务、充分利用多核 cluster 多进程共享端口,主进程保活与重启,横向扩展吞吐
调用外部程序/脚本、任务解耦 child_process 并行执行命令或脚本,隔离性好
计算密集(图像处理、视频转码等) worker_threads 避免阻塞事件循环,利用多核;可共享内存
大量 I/O 并发(DB/缓存/文件/网络) 异步 I/O + 事件循环 非阻塞 I/O 与 epoll 高效等待,通常无需额外线程
需要共享内存与细粒度并行 worker_threads + SharedArrayBuffer 减少拷贝,提升 CPU 密集任务并行效率

关键实践与注意事项

  • 进程与线程治理:为 cluster 配置 退出重启监听异常日志归集;为 worker_threads 做好 异常捕获线程退出码 处理,避免僵尸进程/线程。
  • 并发控制:对海量并发任务使用 有限并发(如信号量/队列)与 超时/重试,防止资源耗尽与级联故障。
  • 共享内存与数据序列化:线程间共享内存可提升性能,但需注意 同步与可见性;跨进程通信需序列化,权衡性能与复杂度。
  • 文件 I/O 认知:Linux 上普通文件 O_NONBLOCK 通常无效,Node 通过 线程池 执行文件 I/O 以不阻塞事件循环;如需极致文件吞吐,考虑 流式处理并发度控制
  • 端口与地址复用:cluster 主进程与工作进程可共享监听套接字,由内核进行 端口复用 与分发,简化多进程部署。

最小可用示例

  • 使用 cluster 启动多进程 HTTP 服务
// server.js
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;

if (cluster.isPrimary) {
  console.log(`主进程 ${process.pid} 启动,CPU 核数: ${numCPUs}`);
  for (let i = 0; i < numCPUs; i++) cluster.fork();
  cluster.on('exit', (worker, code, signal) => {
    console.warn(`工作进程 ${worker.process.pid} 退出 (${signal || code}),重启中...`);
    cluster.fork();
  });
} else {
  http.createServer((req, res) => {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end(`Hello from worker ${process.pid}\n`);
  }).listen(3000, () => {
    console.log(`Worker ${process.pid} 监听 3000`);
  });
}
  • 使用 worker_threads 并行计算
// worker.js
const { Worker, isMainThread, parentPort, workerData } = require('worker_threads');

if (isMainThread) {
  function runWorker(data) {
    return new Promise((resolve, reject) => {
      const worker = new Worker(__filename, { workerData: data });
      worker.on('message', resolve);
      worker.on('error', reject);
      worker.on('exit', (code) => {
        if (code !== 0) reject(new Error(`Worker 退出码 ${code}`));
      });
    });
  }

  (async () => {
    const results = await Promise.all(
      Array.from({ length: 4 }, (_, i) => runWorker({ taskId: i, n: 1e8 }))
    );
    console.log('All done:', results);
  })();
} else {
  // 模拟 CPU 密集任务
  const { taskId, n } = workerData;
  let sum = 0;
  for (let i = 0; i < n; i++) sum += i;
  parentPort.postMessage({ taskId, sum });
}
  • 使用 child_process 并行执行命令
// spawnTasks.js
const { spawn } = require('child_process');
const tasks = ['sleep 1 && echo A', 'sleep 2 && echo B', 'sleep 1 && echo C'];

tasks.forEach(cmd => {
  const [prog, ...args] = cmd.split(' ');
  const child = spawn(prog, args);
  child.stdout.on('data', data => process.stdout.write(data));
  child.stderr.on('data', data => process.stderr.write(data));
  child.on('close', code => console.log(`[${cmd}] 退出码: ${code}`));
});
  • 使用 Promise/async-await 控制并发
// limitedParallel.js
const pLimit = require('p-limit'); // 需 npm i p-limit
const limit = pLimit(10); // 同时最多 10 个

async function task(i) {
  // 模拟异步任务
  await new Promise(r => setTimeout(r, Math.random() * 1000));
  return `task-${i}`;
}

(async () => {
  const tasks = Array.from({ length: 100 }, (_, i) => limit(() => task(i)));
  const results = await Promise.all(tasks);
  console.log('完成数量:', results.length);
})();

上述示例覆盖了 clusterworker_threadschild_process并发控制 的常见用法,可直接在 Linux 环境运行并根据业务调整并发度与容错策略。

0