节点创建进程作为核心数

Node create processes as the number of cores

来自 Node.JS 文档:

这些子节点仍然是 V8 的全新实例。假设每个新节点至少需要 30 毫秒的启动时间和 10 MB 的内存。也就是说,您无法创建数千个。

结论最好的做法是按照 CPU 核心的数量进行分叉,即:

var cluster = require('cluster');
var http = require('http');
var numCPUs = require('os').cpus().length;

if (cluster.isMaster) {
  // Fork workers.
  for (var i = 0; i < numCPUs; i++) {
    cluster.fork();
  }

  cluster.on('death', function(worker) {
    console.log('worker ' + worker.pid + ' died');
    cluster.fork();
  });
} else {
  // Worker processes have a http server.
  http.Server(function(req, res) {
    res.writeHead(200);
    res.end("hello world\n");
  }).listen(8000);
}

但是如果假设我们有 4 个核心,我们创建 4 个进程 + 主进程,所以我们总共将有 5 个进程,再次超过 cpu 的核心。

这样有效率吗?

作为一般性答案,您应该分叉与 number of CPUs - 1 一样多的进程,因为您应该留下一个核心 SO 来管理服务器上的其他进程(cron?logrotate?随便什么)。

此外,您派生的进程可以使用相同的 CPU,因为默认情况下 Processor Affinity is managed by the OS. (here an example to customize it and a good answer on Whosebug.

因此,这主要取决于您的应用程序在做什么,因为分叉根本无法从中受益。 Fork for high-intensive CPU work (like crypto or some blocking event loop actions) will benefit from this optimization.

例如,使用您的脚本和一个简单的基准

npx autocannon -c 100 -d 5 -p 10 localhost:8000/:

┌───────────┬────────┬────────┬─────────┬─────────┬─────────┬──────────┬────────┐
│ Stat      │ 1%     │ 2.5%   │ 50%     │ 97.5%   │ Avg     │ Stdev    │ Min    │
├───────────┼────────┼────────┼─────────┼─────────┼─────────┼──────────┼────────┤
│ Req/Sec   │ 46943  │ 46943  │ 71039   │ 79039   │ 68444.8 │ 11810.39 │ 46930  │
├───────────┼────────┼────────┼─────────┼─────────┼─────────┼──────────┼────────┤
│ Bytes/Sec │ 6.1 MB │ 6.1 MB │ 9.23 MB │ 10.3 MB │ 8.9 MB  │ 1.54 MB  │ 6.1 MB │
└───────────┴────────┴────────┴─────────┴─────────┴─────────┴──────────┴────────┘

然后将相同的 autocannon 脚本发送到没有 fork 的端点:

var http = require('http')

// Worker processes have a http server.
http.Server(function (req, res) {
  res.writeHead(200)
  res.end('hello world\n')
}).listen(8000)
┌───────────┬─────────┬─────────┬────────┬─────────┬─────────┬─────────┬─────────┐
│ Stat      │ 1%      │ 2.5%    │ 50%    │ 97.5%   │ Avg     │ Stdev   │ Min     │
├───────────┼─────────┼─────────┼────────┼─────────┼─────────┼─────────┼─────────┤
│ Req/Sec   │ 48063   │ 48063   │ 62303  │ 63167   │ 59632   │ 5807.42 │ 48040   │
├───────────┼─────────┼─────────┼────────┼─────────┼─────────┼─────────┼─────────┤
│ Bytes/Sec │ 6.25 MB │ 6.25 MB │ 8.1 MB │ 8.21 MB │ 7.75 MB │ 755 kB  │ 6.25 MB │
└───────────┴─────────┴─────────┴────────┴─────────┴─────────┴─────────┴─────────┘

这里没有分叉并没有那么慢!!

但是如果我们用high-CPU操作改变端点:

http.Server(function (req, res) {
  res.writeHead(200)
  for (var i = 0; i < 999999; i++) {
    // cpu cycle waste
  }
  res.end('hello world\n')
}).listen(8000)

我们会得到没有叉子:

┌───────────┬────────┬────────┬────────┬────────┬────────┬─────────┬────────┐
│ Stat      │ 1%     │ 2.5%   │ 50%    │ 97.5%  │ Avg    │ Stdev   │ Min    │
├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤
│ Req/Sec   │ 1671   │ 1671   │ 1790   │ 1870   │ 1784.2 │ 76.02   │ 1671   │
├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤
│ Bytes/Sec │ 217 kB │ 217 kB │ 233 kB │ 243 kB │ 232 kB │ 9.88 kB │ 217 kB │
└───────────┴────────┴────────┴────────┴────────┴────────┴─────────┴────────┘

然后用fork+高强度CPU操作有了很大的提升!!

┌───────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐
│ Stat      │ 1%      │ 2.5%    │ 50%     │ 97.5%   │ Avg     │ Stdev   │ Min     │
├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤
│ Req/Sec   │ 8575    │ 8575    │ 9423    │ 9823    │ 9324    │ 421.9   │ 8571    │
├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤
│ Bytes/Sec │ 1.12 MB │ 1.12 MB │ 1.22 MB │ 1.28 MB │ 1.21 MB │ 54.7 kB │ 1.11 MB │
└───────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘

所以我认为您不应该 "pre optimize" 一个 HTTP 端点,因为它可能需要大量工作而不费力。