Node.js:进程、子进程与cluster多核处理模块
1、process对象
process
对象就是处理与进程相关信息的全局对象,不需要require引用,且是EventEmitter的实例。
获取进程信息
process对象提供了很多的API来获取当前进程的运行信息,例如进程运行时间、内存占用、CPU占用、进程号等,具体使用如下所示:
/**
* 获取当前Node.js进程信息
*/
function getProcessInfo(){
const memUsage = process.memoryUsage();//内存使用
const cpuUsage = process.cpuUsage();//cpu使用
const cfg = process.config;//编译node.js的配置信息
const env = process.env;//用户环境
const pwd = process.cwd();//工作目录
const execPath = process.execPath;//node.exe目录
const pf = process.platform;//运行nodejs的操作系统平台
const release = process.release;//nodejs发行版本
const pid = process.pid;//nodejs进程号
const arch = process.arch;//运行nodejs的操作系统架构
const uptime = process.uptime();//nodejs进程运行时间
return {
memUsage,
cpuUsage,
cfg,
env,
pwd,
execPath,
pf,
release,
pid,
arch,
uptime
}
}
console.log(getProcessInfo());
process.argv获取命令行指令参数
使用node命令执行某个脚本时,可以在指令末尾加上参数,process.argv
返回一个数组,第一个元素是process.execPath
,第二个元素是被执行脚本的路径,如下所示:
var args = process.argv;
if(!args.length){
process.exit(0);
}else{
console.log(args.slice(2).join('\n'));
}
执行结果如下:
E:\developmentdocument\nodejsdemo>node process-example.js a b c
a
b
c
process事件
1、exit事件,当调用process.exit()
方法或者事件循环队列没有任何工作时便会触发该事件,监听的回调函数的逻辑必须是同步的,否则不会执行。如下所示:
process.on('exit',(code)=>{
console.log(code);
setTimeout(()=>console.log(code),1000);//不会执行
});
2、uncaughtException事件,当一个没有被捕获的异常冒泡到事件队列就会触发该事件,默认打印错误信息并进程退出,当uncaughtException事件有一个以上的 listener 时,会阻止 Node 结束进程。但是这种做法有内存泄露的风险,所以千万不要这么做。如下所示:
process.on('uncaughtException',(err)=>{
console.log(err);
});
setTimeout(()=>console.log('nihao'),1000);//1秒后会执行
a();
console.log('hehe');//不会执行
3、message事件,进程间使用childProcess.send()
方法进行通信,就会触发该事件,使用如下所示:
const cp = require('child_process').fork(`${__dirname}/test.js`);
cp.on('message',(message)=>{
console.log('got the child message:'+message);
});
cp.send('hello child!');
//test.js
process.on('message',(message)=>{
console.log('got the parent message:'+message);
});
process.send('hello parent');
执行结果如下:
E:\developmentdocument\nodejsdemo>node process-example.js
got the child message:hello parent
got the parent message:hello child!
process.nextTick方法
将回调函数添加到下一次事件缓存队列中,当前事件循环都执行完毕后,所有的回调函数都会被执行,如下所示:
console.log('hello world');
setTimeout(()=>console.log('settimeout'),10);
process.nextTick(()=>console.log('nexttick'));
console.log('hello nodejs');
执行结果如下所示:
E:\developmentdocument\nodejsdemo>node process-example.js
hello world
hello nodejs
nexttick
settimeout
2、child_process模块
通过child_process模块可以创建子进程,从而实现多进程模式,更好地利用CPU多核计算资源。该模块提供了四种方法创建子进程,分别是child_process.spawn()
、child_process.exec()
、child_process.execFile()
,child_process.fork()
,这四个方法都返回一个childProcess
对象,该对象实现了EventEmitter的接口,带有stdout,stdin,stderr的对象。
child_process.spawn(command[, args][, options])方法
该方法使用command指令创建一个新进程,参数含义如下:
- command,带执行的命令
- args,命令行参数数组
- options,可选参数,为一个对象
options参数主要拥有以下属性:
- cwd,当前工作目录,若没有指定,则使用当前工作目录
- env,命令执行环境,默认为process.env
- argv0,如果没有指定command,该值会被设置为command
- stdio,子进程标准IO配置
返回值为childProcess对象,使用如下所示:
const child_process = require('child_process');
const iconv = require('iconv-lite');
const spawn = child_process.spawn;
const buffArr = [];
let buffLen = 0;
const dirs = spawn('cmd.exe',['/C','dir']);
dirs.stdout.on('data',(data)=>{
buffArr.push(data);
buffLen+=data.length;
});
dirs.stderr.on('end',()=>{
console.log(iconv.decode(Buffer.concat(buffArr,buffLen),'GBK'));
});
dirs.stderr.on('error',(err)=>{
console.log(err);
});
dirs.on('close',(code)=>{
console.log(code);
});
执行结果如下:
正在 Ping www.qq.com [14.17.32.211] 具有 32 字节的数据:
来自 14.17.32.211 的回复: 字节=32 时间=2ms TTL=55
来自 14.17.32.211 的回复: 字节=32 时间=2ms TTL=55
来自 14.17.32.211 的回复: 字节=32 时间=3ms TTL=55
来自 14.17.32.211 的回复: 字节=32 时间=3ms TTL=55
14.17.32.211 的 Ping 统计信息:
数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失),
往返行程的估计时间(以毫秒为单位):
最短 = 2ms,最长 = 3ms,平均 = 2ms
如果输出碰到乱码的时候,可以借助iconv-lite进行转码即可,使用npm install iconv-lite --save
。
child_process.exec(command[, options][, callback])方法
新建一个shell执行command指令,并缓存产生的输出结果,方法参数含义如下:
- command,待执行的指令,带独立的参数
- options,对象,拥有cwd,env,encoding,shell,maxBuffer等属性
- callback,回调函数,参数为(error,stdout,stderr),如果执行成功,error则为null,否则为Error的实例。
返回值也是childProcess对象,该方法与child_process.spawn()
方法的区别在于,使用回调函数获得子进程的输出数据,会先将数据缓存在内存中,等待子进程执行完毕之后,再将所有的数据buffer交给回调函数,如果该数据大小超过了maxBuffer(默认为200KB),则会抛出错误。虽然可以通过参数maxBuffer来设置子进程的缓存大小,但是不建议这么做,因为exec()
方法不合适创建返回大量数据的进程,应该就返回一些状态码。
使用如下所示:
exec('netstat /ano | find /C /I "tcp"',(err,stdout,stderr)=>{
if(err) throw err;
console.log(stdout);
console.log(stderr);
});
child_process.execFile(file[, args][, options][, callback])方法
类似与child_process.exec()
方法,不同之处是不会创建一个shell,而是直接使用指定的可执行文件创建一个新进程,更有效率,使用如下所示:
execFile('mysql',['--version'],(err,stdout,stderr)=>{
if(err) throw err;
console.log(stdout);
console.log(stderr);
});
child_process.fork(modulePath[, args][, options])方法
创建一个子进程执行module,并与子进程建立IPC通道进行通信,方法返回一个childProcess对象,作为子进程的句柄,通过send()方法向子进程发送信息,监听message事件接收子进程的消息,子进程亦同理。使用如下所示:
const fibonacci = fork('./fibonacci.js');
const n = 10;
fibonacci.on('message',(msg)=>{
console.log(`fibonacci ${n} is:${msg.result}`);
});
fibonacci.send({n:n});
//fibonacci.js
function fibonacci(n,ac1=1,ac2=1){
return n<=2?ac2:fibonacci(n-1,ac2,ac1+ac2);
}
process.on('message',(msg)=>{
process.send({result:fibonacci(msg.n)})
});
child.disconnect()方法
关闭父子进程之间的IPC通道,之后父子进程不能执行通信,并会立即触发disconnect
事件,使用如下所示:
const fibonacci = fork('./fibonacci.js');
const n = 10;
fibonacci.on('message',(msg)=>{
console.log(`fibonacci ${n} is:${msg.result}`);
fibonacci.disconnect();
});
fibonacci.on('disconnect',()=>{
console.log('与子进程断开连接.');
});
fibonacci.send({n:n});
//fibonacci.js
function fibonacci(n,ac1=1,ac2=1){
return n<=2?ac2:fibonacci(n-1,ac2,ac1+ac2);
}
process.on('message',(msg)=>{
process.send({result:fibonacci(msg.n)})
});
执行结果:
fibonacci 10 is:55
与子进程断开连接.
子进程主要用来做CPU密集型的工作,如fibonacci数列的计算,canvas像素处理等。
3、cluster多核处理模块
Node.js是单线程运行的,不管你的机器有多少个内核,只能用到其中的一个,为了能利用多核计算资源,需要使用多进程来处理应用。cluster模块让我们可以很容易地创建一个负载均衡的集群,自动分配CPU多核资源。
使用如下所示:
const cluster = require('cluster');
const http = require('http');
const cpuNums = require('os').cpus().length;
if(cluster.isMaster){
for(let i=0;i<cpuNums;i++){
cluster.fork();
}
cluster.on('exit',(worker)=>{
console.log(`worker${worker.id} exit.`)
});
cluster.on('fork',(worker)=>{
console.log(`fork:worker${worker.id}`)
});
cluster.on('listening',(worker,addr)=>{
console.log(`worker${worker.id} listening on ${addr.address}:${addr.port}`)
});
cluster.on('online',(worker)=>{
console.log(`worker${worker.id} is online now`)
});
}else{
http.createServer((req,res)=>{
console.log(cluster.worker.id);
res.writeHead(200);
res.end('hello world');
}).listen(3000,'127.0.0.1');
}
执行结果:
fork:worker1
fork:worker2
fork:worker3
fork:worker4
worker1 is online now
worker2 is online now
worker3 is online now
worker1 listening on 127.0.0.1:3000
worker4 is online now
worker2 listening on 127.0.0.1:3000
worker3 listening on 127.0.0.1:3000
worker4 listening on 127.0.0.1:3000
cluster工作原理
如上代码所示,master是控制进程,worker是执行进程,每个worker都是使用child_process.fork()
函数创建的,因此worker与master之间通过IPC进行通信。
当worker调用用server.listen()方法时会向master进程发送一个消息,让它创建一个服务器socket,做好监听并分享给该worker。如果master已经有监听好的socket,就跳过创建和监听的过程,直接分享。换句话说,所有的worker监听的都是同一个socket,当有新连接进来的时候,由负载均衡算法选出一个worker进行处理。
cluster对象的属性和方法
cluster.isMaster:标志是否master进程,为true则是
cluster.isWorker:标志是否worker进程,为true则是
cluster.worker:获得当前的worker对象,在master进程中使用无效
cluster.workers: 获得集群中所有存活的worker对象,子啊worker进程使用无效
cluster.fork(): 创建工作进程worker
cluster.disconnect([callback]): 断开所有worker进程通信
*cluster对象的事件
Event: 'fork': 监听创建worker进程事件
Event: 'online': 监听worker创建成功事件
Event: 'listening': 监听worker进程进入监听事件
Event: 'disconnect': 监听worker断开事件
Event: 'exit': 监听worker退出事件
Event: 'message':监听worker进程发送消息事件
使用如下所示:
const cluster = require('cluster');
const http = require('http');
const cpuNums = require('os').cpus().length;
/*process.env.NODE_DEBUG='net';*/
if(cluster.isMaster){
for(let i=0;i<cpuNums;i++){
cluster.fork();
}
cluster.on('exit',(worker)=>{
console.log(`worker${worker.id} exit.`)
});
cluster.on('fork',(worker)=>{
console.log(`fork:worker${worker.id}`)
});
cluster.on('disconnect',(worker)=>{
console.log(`worker${worker.id} is disconnected.`)
});
cluster.on('listening',(worker,addr)=>{
console.log(`worker${worker.id} listening on ${addr.address}:${addr.port}`)
});
cluster.on('online',(worker)=>{
console.log(`worker${worker.id} is online now`)
});
cluster.on('message',(worker,msg)=>{
console.log(`got the worker${worker.id}'s msg:${msg}`);
});
Object.keys(cluster.workers).forEach((id)=>{
cluster.workers[id].send(`hello worker${id}`);
});
}else{
process.on('message',(msg)=>{
console.log('worker'+cluster.worker.id+' got the master msg:'+msg);
});
process.send('hello master, I am worker'+cluster.worker.id);
http.createServer((req,res)=>{
res.writeHead(200);
res.end('hello world'+cluster.worker.id);
}).listen(3000,'127.0.0.1');
}
执行结果如下:
fork:worker1
fork:worker2
fork:worker3
fork:worker4
worker1 is online now
worker2 is online now
got the worker1's msg:hello master, I am worker1
worker1 got the master msg:hello worker1
worker1 listening on 127.0.0.1:3000
worker4 is online now
got the worker2's msg:hello master, I am worker2
worker2 got the master msg:hello worker2
worker3 is online now
worker2 listening on 127.0.0.1:3000
got the worker4's msg:hello master, I am worker4
worker4 got the master msg:hello worker4
worker4 listening on 127.0.0.1:3000
got the worker3's msg:hello master, I am worker3
worker3 got the master msg:hello worker3
worker3 listening on 127.0.0.1:3000
在win7环境下,cluster负载均衡情况,如下所示:
服务端代码:
const cluster = require('cluster');
const http = require('http');
const cpuNums = require('os').cpus().length;
if(cluster.isMaster){
var i = 0;
const widArr = [];
for(let i=0;i<cpuNums;i++){
cluster.fork();
}
cluster.on('message',(worker,msg)=>{
if(msg === 'ex'){
i++;
widArr.push(worker.id);
(i>=80)&&(process.exit(0));
}
});
process.on('exit', (code) => {
console.log(analyzeArr(widArr));
});
//统计每个worker被调用的次数
function analyzeArr(arr) {
let obj = {};
arr.forEach((id, idx, arr) => {
obj['work' + id] = obj['work' + id] !== void 0 ? obj['work' + id] + 1 : 1;
});
return obj;
}
}else{
http.createServer((req,res)=>{
console.log(`worker${cluster.worker.id}`);
process.send('ex');
res.writeHead(200);
res.end('hello world'+cluster.worker.id);
}).listen(3000,'127.0.0.1');
}
使用Apache的AB命令进行测试,并发40,总共80:C:\Users\learn>ab -c 40 -n 80 http://127.0.0.1:3000/
。
测试结果:
{ work4: 19, work3: 20, work1: 19, work2: 22 }
Node.js:进程、子进程与cluster多核处理模块的更多相关文章
- Node.js进程管理之子进程
一.理论 之前看多进程这一章节时发现这块东西挺多,写Process模块的时候也有提到,今天下午午休醒来静下心来好好的看了一遍,发现也不是太难理解. Node.js是单线程的,对于现在普遍是多处理器的机 ...
- 深入理解 Node.js 进程与线程
原文链接: https://mp.weixin.qq.com/s?__biz=MzAxODE2MjM1MA==&mid=2651557398&idx=1&sn=1fb991da ...
- Node.js进程管理之Process模块
在前面Node.js事件运行机制也有提到,Node.js应用在单个线程运行,但是现在大部分服务器都是多处理器,为了方便使用多个进程,Node.js提供了3个模块.Process模块提供了访问正在运行的 ...
- 避免uncaughtException错误引起node.js进程崩溃
uncaughtException 未捕获的异常, 当node.js 遇到这个错误,整个进程直接崩溃. 或许这俩个人上辈子一定是一对冤家. 或许这俩个人经历了前世500次的回眸才换来了今生的相遇,只可 ...
- Node.js学习笔记(二):模块
模块是 Node.js 应用程序的基本组成部分,文件和模块是一一对应的.一个 Node.js 文件就是一个模块,这个文件可能是 JavaScript 代码.JSON 或者编译过的 C/C++ 扩展. ...
- node.js(七) 子进程 child_process模块
众所周知node.js是基于单线程模型架构,这样的设计可以带来高效的CPU利用率,但是无法却利用多个核心的CPU,为了解决这个问题,node.js提供了child_process模块,通过多进程来实现 ...
- Node.js进程管理之进程集群
一.cluster模块 Node.js是单线程处理,对于高并发的请求怎么样能增加吞吐量呢?为了提高服务器的利用率,能不能多核的来处理呢?于是就有了cluster模块. cluster模块可以轻松实现运 ...
- Node.js进程通信模块child_process
前言 Node.js是一种单线程的编程模型,对Node.js的赞美和诟病的也都是因为它的单线程模型,所有的任务都在一个线程中完成(I/O等例外).单线程模型,不仅让代码非常简洁,更是直接避免了线程调度 ...
- 拿什么守护你的Node.JS进程: Node出错崩溃了怎么办? foreverjs, 文摘随笔
守护进程 方案一 npm install forever https://github.com/foreverjs/forever 方案二 npm install -g supervisor http ...
随机推荐
- Hadoop 中利用 mapreduce 读写 mysql 数据
Hadoop 中利用 mapreduce 读写 mysql 数据 有时候我们在项目中会遇到输入结果集很大,但是输出结果很小,比如一些 pv.uv 数据,然后为了实时查询的需求,或者一些 OLAP ...
- vue2.0实践的一些细节
最近用vue2.0做了个活动.做完了回头发现,好像并没有太多的技术难点,而自己好像又做了比较久...只能说效率有待提升啊...简单总结了一些比较细节的点. 1.对于一些已知肯定会有数据的模块,先用一个 ...
- 开源:Taurus.MVC 框架
为什么要创造Taurus.MVC: 记得被上一家公司忽悠去负责公司电商平台的时候,情况是这样的: 项目原版是外包给第三方的,使用:WebForm+NHibernate,代码不堪入目,Bug无限,经常点 ...
- HTML5 Boilerplate - 让页面有个好的开始
最近看到了HTML5 Boilerplate模版,系统的学习与了解了一下.在各种CSS库.JS框架层出不穷的今天,能看到这么好的HTML模版,感觉甚爽.写篇博客,推荐给大家使用. 一:HTML5 ...
- 干货分享:SQLSERVER使用裸设备
干货分享:SQLSERVER使用裸设备 这篇文章也适合ORACLE DBA和MYSQL DBA 阅读 裸设备适用于Linux和Windows 在ORACLE和MYSQL里也是支持裸设备的!! 介绍 大 ...
- Velocity笔记--使用Velocity获取动态Web项目名的问题
以前使用jsp开发的时候,可以通过request很轻松的获取到根项目名,现在换到使用velocity渲染视图,因为已经不依赖servlet,request等一些类的环境,而Web项目的根项目名又不是写 ...
- 高德地图api实现地址和经纬度的转换(python)
利用高德地图web服务api实现地理/逆地址编码 api使用具体方法请查看官方文档 文档网址:http://lbs.amap.com/api/webservice/guide/api/georegeo ...
- PHP设计模式(八)桥接模式(Bridge For PHP)
一.概述 桥接模式:将两个原本不相关的类结合在一起,然后利用两个类中的方法和属性,输出一份新的结果. 二.案例 1.模拟毛笔(转) 需求:现在需要准备三种粗细(大中小),并且有五种颜色的比 如果使用蜡 ...
- BPM配置故事之案例11-操作外部数据源
小明:可以获取ERP数据了-- 老李:哦,这么快?小伙子,我非常看好你,来来,别急着走,再陪我聊会-- 小明:--您老人家不是又要改流程吧? 老李:没有没有,哎嘿嘿嘿,我们这不都是为公司效率着想嘛,这 ...
- 基于开源项目SharpMap的热力图(HeatLayer)实现。
当前公司需要一个用时较少的热力图呈现方案,在避免较底层的GDI开发和比较了多家GIS产品的实际效果之后,团队决定用sharpMap的API来实现,由于之前框架采用的是另外一个开源项目GMap.net, ...