节点创建进程作为核心数
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 端点,因为它可能需要大量工作而不费力。
来自 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 端点,因为它可能需要大量工作而不费力。