每个Node.js都是单线程的,这样其实也存在一定的缺点:1. 线程崩溃则程序崩溃;2. 没有充分用起来多核CPU的优势;
Node.js注定不会引入语言级别的多线程,这是不可能的。
但是,Node.js提供了多进程模块,通过开启多个进程来解决上面的两个问题,对应就是其内置的child_process模块。
四种模式
四种模式开启子进程:spawn、fork、exec、execFile,对应的就是child_process的四个方法。逐一看下:
spawn
这个词是产卵的意思….
spawn是使用指定的命令行参数创建新进程,语法格式如下:
child_process.spawn(command[, args][, options])
举个例子:
child_process.spawn('node', ['support.js']);
上面的例子中其实开启子进程的是: $ node support.js,所以我们看到,其实就是开了一个进程执行命令而已,只不过这种模式下,命令是被分解的,比如数组里面的就是命令的参数,比如现在执行命令:$ node support.js dev,我们给support.js传递了参数dev,那么对应的代码就是:child_process.spawn(‘node’, [‘support.js’, ‘dev’]);
(support.js如何拿到这个参数?process.argv[2],参见process.argv文档)
主进程中拿到子进程的执行结果是:
const workerProcess = child_process.spawn('node', ['support.js', 'dev']);
workerProcess.stdout.on('data', function (data) {
console.log('stdout: ' + data);
});
exec
child_process.exec 使用子进程执行命令,缓存子进程的输出,并将子进程的输出以回调函数参数的形式返回。
简单点来说,就是比spawn在命令方面爽快:
child_process.exec(command[, options], callback)
exec不用把一个命令拆开,而是一条命令就是一个字符串;另外,在拿到返回数据的时候,是通过传入callback拿到的,具体看代码:
const workerProcess = child_process.exec('node support.js dev', function (error, stdout, stderr) {
if (error) {
// ...
}
console.log('stdout: ' + stdout);
console.log('stderr: ' + stderr);
});
execFile
execFile从命名上像exec,但是我觉得更像spawn,它和spawn的唯一区别就是获得返回值方式不同。
可以这么理解,execFile传递参数和spawn一样,返回值的获得和exec一样。
fork
fork才是最常用的。
child_process.fork(modulePath[, args][, options])
modulePath,望文生义,什么东西才能称之为module?自然是js模块,所以,fork其实可以理解成是特殊的spawn:
就是fork(‘./son.js’) 对应 spawn(‘node’, [‘./son.js’])。
在Linux中,创建进程,本质就是一个复制的过程,当用户调用了fork,操作系统就为这个新的进程分配空间,然后将父进程的数据(内存中的代码,数据等)原样复制,父子进程间只有PID等个别属性不同。
不过,代码中有方式来判断是不是子进程,这就为相同代码下执行不同任务的需求提供了可能性,这是后话。
进程间通信
child_process虽然是小写,但是可以作为类来理解,所以刚才构造进程的方法比如fork,可以理解这就是这个类的类方法,同时,child_process又继承了EventEmitter,那么,其实例就有on和emit的能力了。
不过对应的on,和send方法。
master.js:
const child_process = require('child_process');
const worker = child_process.fork('./worker.js', ['csgWorker']);
worker.on('exit', () => {
console.log('child exist')
});
// worker.send,理解为向worker发送
worker.send('hello son');
// 监听来自worker的消息
worker.on('message', msg => {
console.log('form son:', msg)
})
worker.js:
console.log('I am worker', process.argv[2])
// 在worker中向父进程发送消息用process.send
process.send('hello yoyoyo, father');
// 在worker中监听来自father的消息
process.on('message', msg => {
console.log('form father:', msg)
process.exit();
})
执行以后输出:
I am worker csgWorker
form father: hello son
form son: hello yoyoyo, father
child exist
另外,进程间通信(Inter-process communication, IPC)这个概念,本质是要进程的数据传递到另外一个进程,这就是 IPC ,实现的方式很多,比如:
在 Node.js 中 IPC 的实现,在 Windows 上通过命名管道,在 Unix 通过 UNIX domain sockets 实现(详见官方文档)。
Cluster
Cluster是Node从0.6版本引入的,可以看作是对child_process做统一管理的类。
Cluster API可以了解细节,但是这里想说一个经典的‘主从模型’。
const cluster = require('cluster');
const numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
// fork工作进程。
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
} else {
// do somthing by worker
}
这个主从模型就是借助统一的进程管理cluster提供的isMater方法,鉴别是主线程还是工作线程,前面说过,fork的本质是OS复制一个同样的数据,开辟不同的内存,这数据自然包括代码,但是cluster.isMaster的鉴别能力,就保证了同样的一份代码,不同身份的进程最终执行的结果不同。
然后再看下一个经典的LB的例子(Load Balance, 负载均衡)
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
console.log(`主进程 ${process.pid} 正在运行`);
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', (worker, code, signal) => {
console.log(`工作进程 ${worker.process.pid} 已退出`);
});
} else {
http.createServer((req, res) => {
res.writeHead(200);
res.end('你好世界\n');
}).listen(8000);
console.log(`工作进程 ${process.pid} 已启动`);
}
我们看到,又几个CPU在master中fork了多少个进程,这样充分利用了多CPU的处理能力。不过这里还有知识点,我们仔细审视代码,发现,每一个进程都在监听8000端口!这样不会端口冲突了吗?
这样想没毛病,如果端口冲突了,一般Node会报错了:Error: listen EADDRIUNS。但是,Cluster能让多个Worker监听同一个端口的原因是,这些Cluster集群中的Worker本质都在复用一个TCP端口。有个专业词叫,round-robin:主进程监听端口,接收到新连接之后,通过时间片轮转法来决定将接收到的客户端的 socket 句柄传递给指定的 worker 处理。至于每个连接由哪个 worker 来处理,完全由内置的循环算法决定。
参考:知乎_Node.js cluster 踩坑小结
守护进程管理工具PM2
PM2 is a daemon process manager that will help you manage and keep your application online. Getting started with PM2 is straightforward, it is offered as a simple and intuitive CLI, installable via NPM.
启动Node.js项目怎么启动?直接node命令吗?
别傻了,如果这么做,那就说明没有工程实践经验,面试等着挂吧。
直接通过node app来启动,如果报错了可能直接停在整个运行。目前似乎最常见的线上部署nodejs项目的有、supervisorforever、pm2等,其中pm2是最流行的。
对比下:
- supervisor是开发环境用。
- forever管理多个站点,每个站点访问量不大,不需要监控。
- nodemon 是开发环境使用,修改自动重启。
- pm2 网站访问量比较大,需要完整的监控界面。
PM2的主要特性: - 内建负载均衡(使用Node cluster 集群模块)
- 后台运行
- 0秒停机重载,我理解大概意思是维护升级的时候不需要停机.
等等…就不列出了。
安装之后,几个简单的命令就能使用,非常简单;