这是我参与12月更文挑战的第17天,活动详情查看:2021最后一次更文挑战
Node作为一个服务端框架,需要运行在服务器上,现在的服务器大多是多核,但是Node进程只能运行在一个cpu核上,如何利用服务器的多核模式呢?
官方给出的解决方案是cluster模块。翻译出来是集群,但是实际上跟常说的后端集群没啥关系。
cluster的特点:
- 同时启动多个进程
- 每个进程里跑的都是同一份代码
- 这些进程可以监听同一个端口
其中分为master进程和worker进程,master进程负责启动、管理其它进程,worker进程负责接收请求及提供服务。
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
// Fork workers.
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', function(worker, code, signal) {
console.log('worker ' + worker.process.pid + ' died');
});
} else {
// Workers can share any TCP connection
// In this case it is an HTTP server
http.createServer(function(req, res) {
res.writeHead(200);
res.end("hello world\n");
}).listen(8000);
}
其中,虽然每个worker都监听了同一个端口,但是实际上通过cluster模块的处理,端口的监听是master来做,所有请求都先经过master服务,然后由master服务根据负载均衡策略挑选出一个worker进程进行处理并返回。
从上面的代码可以看出来,worker进程遇到异常,没有做任何处理。此时有两种情况:
- 未捕获异常 这种情况,可以通过process.on('uncaughtException')来处理,通常应该让worker执行完程之后关闭并重新建立一个新的worker进程
- OOM、系统异常 这时候只能让进程立即退出并fork一个新的worker
所有的worker执行同一份代码,这在某些场景下不合适,比如定时备份日志的场景,如果每个进程在一个特定时间都执行一下备份日志的逻辑,会浪费资源且有可能产生冲突。
这时候egg框架提供了一个agent的身份,用来处理公共事务。注意agent不应该处理一般性的业务逻辑。它在可靠性要求及错误处理上都与worker有所差异,具体可以查看egg文档。
某些场景下还是需要进程间通信的能力。这个可以借助于cluster提供的api来实现。
'use strict';
const cluster = require('cluster');
if (cluster.isMaster) {
const worker = cluster.fork();
worker.send('hi there');
worker.on('message', msg => {
console.log(`msg: ${msg} from worker#${worker.id}`);
});
} else if (cluster.isWorker) {
process.on('message', (msg) => {
process.send(msg);
});
}
可以看出来cluster模块基于child_process.fork的一系列封装,能让我们更为方便安全地开发多进程服务。如果有更为底层及细致的多进程需求,还是要借助于child_process模块。