深入理解Node.js中的进程管理
前言
本文主要对Node.js中进程管理相关的东西做一个简单介绍,包括process对象、child_process模块和cluster模块,详细的API可以查看官方文档,下面来看看详细的介绍吧。
Process对象
process是Node.js的一个全局对象,可以在任何地方直接使用而不需要require命令加载。process对象提供了当前node进程的命令行参数、标准输入输出、运行环境和运行状态等信息。
常用属性
argv
process.argv属性返回一个数组,第一个元素是node,第二个元素是脚本文件名称,其余成员是脚本文件的参数。
$nodeprocess-2.jsonetwo=threefour 0:/usr/local/bin/node 1:/Users/mjr/work/node/process-2.js 2:one 3:two=three 4:four
env
process.env返回一个对象,包含了当前Shell的所有环境变量,比如:
{
TERM:'xterm-256color',
SHELL:'/bin/zsh',
USER:'huangtengfei',
PATH:'~/.bin/:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin',
PWD:'/Users/huangtengfei',
HOME:'/Users/huangtengfei'
}
这个属性通常的使用场景是,新建一个NODE_ENV变量,用来确定当前所处的开发阶段,生成阶段设为production,开发阶段设为develop,然后在脚本中读取process.env.NODE_ENV再做相应处理即可。
运行脚本时可以这样改变环境变量:
$exportNODE_ENV=production&&nodeapp.js #或者 $NODE_ENV=productionnodeapp.js
stdin/stdout
process.stdin指向标准输入(键盘到缓冲区里的东西),返回一个可读的流:
process.stdin.setEncoding('utf8');
process.stdin.on('readable',()=>{
varchunk=process.stdin.read();
if(chunk!==null){
process.stdout.write(`data:${chunk}`);
}
});
process.stdin.on('end',()=>{
process.stdout.write('end');
});
process.stdout指向标准输出(向用户显示内容),返回一个可写的流:
constfs=require('fs');
fs.createReadStream('wow.txt')
.pipe(process.stdout);
常用方法
cwd()
process.cwd()返回运行Node的工作目录(绝对路径),比如在目录/Users/huangtengfei/abc下执行nodeserver.js,那么process.cwd()返回的就是/Users/huangtengfei/abc。
另一个常用的获取路径的方法是__dirname,它返回的是执行文件时该文件在文件系统中所在的目录。注意process.cwd()和__dirname的不同,前者是进程发起时的位置,后者是脚本的位置,两者可能不一致。
on()
process对象部署了EventEmitter接口,可以使用process.on()方法监听各种事件,并指定回调函数。比如监听到系统发出进程终止信号时关闭服务器然后退出进程:
process.on('SIGTERM',function(){
server.close(function(){
process.exit(0);
});
});
exit()
process.exit()会让Node立即终止当前进程(同步),参数为一个退出状态码,0表示成功,大于0的任意整数表示失败。
kill()
process.kill()用来对特定id的进程(process.pid)发送信号,默认为SIGINT信号。比如杀死当前进程:
process.kill(process.pid,'SIGTERM');
虽然名字叫kill,但其实process.kill()只是负责发送信号,具体发送完信号之后这个怎么处理这个指定进程,取决于信号种类和接收到这个信号之后做了什么操作(比如process.exit()或者只是console.log('Ignoredthissingle'))。
ChildProcess模块
child_process模块用于创建和控制子进程,其中最核心的是.spawn(),其他API算是针对特定场景对它的封装。使用前要先require进来:
constcp=require('child_process');
exec(command[,options][,callback])
exec()方法用于执行shell命令,它的第一个参数是字符串形式的命令,第二个参数(可选)用来指定子进程运行时的定制化操作,第三个参数(可选)用来设置执行完命令的回调函数。比如在一个特定目录/Users/huangtengfei/abc下执行ls-l命令:
cp.exec('ls-l',{
cwd:'/Users/huangtengfei/abc'
},(error,stdout,stderr)=>{
if(error){
console.error(`execerror:${error}`);
return;
}
console.log(`stdout:${stdout}`);
console.log(`stderr:${stderr}`);
})
spawn(command[,args][,options])
spawn()用来创建一个子进程执行特定命令,与exec()的区别是它没有回调函数,只能通过监听事件来获取运行结果,它适用于子进程长时间运行的情况,可以实时输出结果。
constls=cp.spawn('ls',['-l']);
ls.stdout.on('data',(data)=>{
console.log(`stdout:${data}`);
});
ls.stderr.on('data',(data)=>{
console.log(`stderr:${data}`);
});
ls.on('close',(code)=>{
console.log(`childprocessexitedwithcode$[code]`);
});
使用spawn可以实现一个简单的守护进程,在工作进程不正常退出时重启工作进程:
/*daemon.js*/
functionspawn(mainModule){
constworker=cp.spawn('node',[mainModule]);
worker.on('exit',function(code){
if(code!==0){
spawn(mainModule);
}
});
}
spawn('worker.js');
fork(modulePath[,args][,options])
fork()用来创建一个子进程执行node脚本,fork('./child.js')相当于spawn('node',['./child.js']),区别在于fork会在父子进程之间建立一个通信管道(fork()的返回值),用于进程间通信。对该通信管道对象可以监听message事件,用来获取子进程返回的信息,也可以向子进程发送信息。
/*main.js*/
constproc=cp.fork('./child.js');
proc.on('message',function(msg){
console.log(`parentgotmessage:${msg}`);
});
proc.send({hello:'world'});
/*child.js*/
process.on('message',function(msg){
console.log(`childgotmessage:${msg}`);
});
process.send({foo:'bar'});
Cluster模块
Node.js默认单进程执行,但这样就无法利用多核计算机的资源,cluster模块的出现就是为了解决这个问题的。在开发服务器程序时,可以通过cluster创建一个主进程和多个worker进程,让每个worker进程运行在一个核上,统一通过主进程监听端口和分发请求。
constcluster=require('cluster');
consthttp=require('http');
constnumCPUs=require('os').cpus().length;
if(cluster.isMaster){
console.log(`Master${process.pid}isrunning`);
//Forkworkers.
for(leti=0;i{
console.log(`worker${worker.process.pid}died`);
});
}else{
//WorkerscanshareanyTCPconnection
//InthiscaseitisanHTTPserver
http.createServer((req,res)=>{
res.writeHead(200);
res.end('helloworld\n');
}).listen(8000);
console.log(`Worker${process.pid}started`);
}
常用属性和方法
isMaster/isWorker
cluster.isMaster用来判断当前进程是否是主进程,cluster.isWorker用来判断当前进程是否是工作进程,两者返回的都是布尔值。
workers
cluster.workers是一个包含所有worker进程的对象,key为worker.id,value为worker进程对象。
//遍历所有workers
functioneachWorker(callback){
for(constidincluster.workers){
callback(cluster.workers[id]);
}
}
eachWorker((worker)=>{
worker.send('bigannouncementtoallworkers');
});
fork([env])
cluster.fork()方法用来新建一个worker进程,默认上下文复制主进程,只有主进程可调用。
常用事件
listening
在工作进程调用listen方法后,会触发一个listening事件,这个事件可以被cluster.on('listening')监听。
比如每当一个worker进程连进来时,输出一条log信息:
cluster.on('listening',(worker,address)=>{
console.log(
`Aworkerisnowconnectedto${address.address}:${address.port}`);
});
exit
在工作进程挂掉时,会触发一个exit事件,这个事件可以被cluster.on('exit')监听。
比如自动重启worker:
cluster.on('exit',(worker,code,signal)=>{
console.log('worker%ddied(%s).restarting...',
worker.process.pid,signal||code);
cluster.fork();
});
worker对象
worker对象是cluster.fork()的返回值,代表一个worker进程。
worker.id
worker.id是当前worker的唯一标识,也是保存在cluster.workers中的key值。
worker.process
所有的worker进程都是通过child_process.fork()生成的,这个进程对象保存在worker.process中。
worker.send()
worker.send()用在主进程给子进程发送消息,在子进程中,使用process.on()监听消息并使用process.send()发送消息。
if(cluster.isMaster){
constworker=cluster.fork();
worker.send('hithere');
}elseif(cluster.isWorker){
process.on('message',(msg)=>{
process.send(msg);
});
}
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对毛票票的支持。