Null's Blog

nodejs笔记4--多进程架构简介

填坑:

1.nodejs笔记1–异步API与其局限性
2.nodejs笔记2–nodejs中的事件通知机制
3.nodejs笔记3–事件通知机制的两个应用场景
4.nodejs笔记4–多进程架构

应用场景

操作系统的资源是有限的,这通常意味者操作系统分配给进程或线程的资源是有限的,在多核cpu普及的时代,如果只使用单核(常说node是单线程的),则无法充分压榨硬件性能.

模块介绍

node提供了child_process模块,该模块提供4个方法
1.spawn() 启动一子进程来执行命令
2.exec() 启动一子进程来执行命令,与spawn()不同的是提供一个回调函数获知子进程的状况
3.execFile() 启动一个子进程来执行可执行文件
4.fork() 与spawn()类似,指定执行的javascript文件模块即可

惯用做法

通常,node项目会有个bin目录,里面的文件类似于应用程序入口,结合commander包实现根据命令行参数执行相应代码逻辑的功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
var child_process = require('child_process');
var fs = require('fs');
var os = require('os');
var program = require('commander');
const EventEmitter = require('events').EventEmitter;
const ee = new EventEmitter();
var config = require('../config.json');
var DEFAULT_TRG_PATH = config.triggersPath;
program.command('start')
.description('start the application')
.option('-d, --directory, <directory>', 'triggers path', DEFAULT_TRG_PATH)
.action(function(opts) {
start(opts);
});
program.command('create')
.description('start create')
.option('-d, --directory, <directory>', 'triggers path', DEFAULT_TRG_PATH)
.action(function(opts) {
create(opts);
});
program.parse(process.argv);
function start(opts){
console.log(1111);
}
function create(opts){
console.log(1111);
}

假设需要执行 start 方式,仅需在命令行带上 start参数即可:

1
node ./bin/www start

同理,其他方法可以通过该方式按需调用.
都不到为啥会扯到commander包的使用,可能是通常情况下想使用多进程架构时功能已经足够复杂,而首先想到的就是用commander包使程序看上去比较好看吧-_-
好了,言归正传,其实我就想使用线程池这种所谓的高级语言封装好的方法而已,可惜node并没有这种.
很多文章都说过,API的用法,这里只简单说说他们之间的区别.

spawn

spawn会返回一个代后stdout与stderr流的对象,可以通过stdout流来读取子进程返回给主进程的数据.如果子进程需要返回大量数据,则应该使用spawn而不是exec. 注意,stdout是以buffer格式返回,相对于exec而言,buffer数据可以很大,也就是说如果主进程需要接收子进程大量的数据时,应该选择spawn来实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
var absScript = path.resolve(process.cwd()+'/lib/', 'child.js');
var child = child_process.spawn(process.argv[0],[absScript,'create','--path=' + dConfig.triggersPath + trgFile]);
child.stdout.on('data', function(data){
console.log(`${data}`);
});
child.stdout.on('end', function(){
console.log('end');
});
child.on('error', function(err){
console.log('Failed to start child process.');
});
child.on('close',function(code){
console.log('closecode:'+ code);
});
//child.js
console.log('modelhunter.js');
var fs = require('fs');
var program = require('commander');
var trgs = require('./components/trgs');
var ModelHunter = module.exports = {};
program.command('create')
.description('create')
.option('-p, --path, <file>', 'triggers path', '')
.action(function(opts) {
if(opts.path.length>0){
console.log(opts.path);
setTimeout(function(){
console.log('aaaaaaa');
process.exit(0);
},1000);
}
});
program.parse(process.argv);

最终输出为

1
2
3
4
5
modelhunter.js
D:/xxxxx.xx
aaaaaaa
end
closecode:0

exec 与 execFile

exec相当于spawn,通讯的内容有限制.适合执行复杂的shell命,与主进程的通讯是一次性的,子进程的结果只能返回一次.
execFile跟exec类型,执行一个文件,或脚本或可执行文件等
exec做了相当的封装,主要用于执行复杂的shell命令.

###fork
fork使用同样的进程复制自身的环境执行脚本,但只能执行node脚本,fork可以很方便的与主进程进行通讯:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//注意这里第一个参数即是目标脚本的路径.
var absScript = path.resolve(process.cwd()+'/lib/', 'modelhunter.js');
var child = child_process.fork(absScript,['create','--path=' + dConfig.triggersPath + trgFile]);
child.on('message',function(m){
console.log('message:'+ m); //接收子进程消息
});
child.on('close',function(code){
console.log(code);
});
child.send('null'); //向子进程发送消息
//child.js
process.on('message',function(m){
console.log('chilid Listen:', m); //接收主进程消息
});
process.send('this childprocess'); //向主进程发送消息

总结

如果想使用多进程架构,按需选择相应的API.即可实现类似”进程池”的功能,当然,可能要配合事件通知机制.
这里提出几点疑问:
1.如果是一个网站的服务端,为了充分利用CPU资源,通常会使用多进程架构,但是我们应该选择fork还是spawn呢?
2.pm2的fork mode 与 cluster mode有什么区别?