node child_process模块
NodeJs是一个单进程的语言,不能像Java那样可以创建多线程来并发执行。当然在大部分情况下,NodeJs是不需要并发执行的,因为它是事件驱动性永不阻塞。但单进程也有个问题就是不能充分利用CPU的多核机制,根据前人的经验,可以通过创建多个进程来充分利用CPU多核,并且Node通过了child_process模块来创建完成多进程的操作。
child_process模块给予node任意创建子进程的能力,node官方文档对于child_proces模块给出了四种方法,映射到操作系统其实都是创建子进程。但对于开发者而已,这几种方法的api有点不同
child_process.exec(command[, options][, callback]) 启动
子进程来执行shell命令,可以通过回调参数来获取脚本shell执行结果
const { exec } = require('child_process');
exec('cat *.js bad_file | wc -l', (error, stdout, stderr) => {
if (error) {
console.error(`exec error: ${error}`);
return;
}
console.log(`stdout: ${stdout}`);
console.log(`stderr: ${stderr}`);
});
child_process.execfile(file[, args][, options][, callback])
与exec类型不同的是,不衍生一个 shell,而是,指定的可执行的 file 被直接衍生为一个新进程,这使得它比 child_process.exec() 更高效。 由于没有衍生 shell,因此不支持像 I/O 重定向和文件查找这样的行为。
const { execFile } = require('child_process');
const child = execFile('node', ['--version'], (error, stdout, stderr) => {
if (error) {
throw error;
}
console.log(stdout);
});
与另外另个命令不同的是接受一个函数,如果提供了一个 callback 函数,则它被调用时会带上参数 (error, stdout, stderr)。 当成功时,error 会是 null。 当失败时,error 会是一个 Error实例。 error.code 属性会是子进程的退出码,error.signal 会被设为终止进程的信号。 除 0 以外的任何退出码都被认为是一个错误。
exec()与execfile()在创建的时候可以指定timeout属性设置超时时间,一旦超时会被杀死
如果使用execfile()执行可执行文件,那么头部一定是#!/usr/bin/env node
child_process.spawn(command[, args][, options])
仅仅执行一个shell命令,不需要获取执行结果
const { spawn } = require('child_process');
const ls = spawn('ls', ['-lh', '/usr']);
ls.stdout.on('data', (data) => {
console.log(`stdout: ${data}`);
});
ls.stderr.on('data', (data) => {
console.log(`stderr: ${data}`);
});
ls.on('close', (code) => {
console.log(`子进程退出码:${code}`);
});
// 例子,一种执行 'ps ax | grep ssh' 的方法:
const { spawn } = require('child_process');
const ps = spawn('ps', ['ax']);
const grep = spawn('grep', ['ssh']);
ps.stdout.on('data', (data) => {
grep.stdin.write(data);
});
ps.stderr.on('data', (data) => {
console.log(`ps stderr: ${data}`);
});
ps.on('close', (code) => {
if (code !== 0) {
console.log(`ps 进程退出码:${code}`);
}
grep.stdin.end();
});
grep.stdout.on('data', (data) => {
console.log(data.toString());
});
grep.stderr.on('data', (data) => {
console.log(`grep stderr: ${data}`);
});
grep.on('close', (code) => {
if (code !== 0) {
console.log(`grep 进程退出码:${code}`);
}
});
child_process.fork(modulePath[, args][, options])
与另外三个不同的是它开启的是一个node进程,执行的只能是js文件。并通过建立 IPC 通讯通道来调用指定的模块,该通道允许父进程与子进程之间相互发送信息。
进程间通信
node 与 子进程之间的通信是使用IPC管道机制完成。如果子进程
也是node进程(使用fork),则可以使用监听message事件和使用send()来通信。
main.js
var cp = require('child_process');
//只有使用fork才可以使用message事件和send()方法
var n = cp.fork('./child.js');
n.on('message',function(m){
console.log(m);
})
n.send({"message":"hello"});
child.js
var cp = require('child_process');
process.on('message',function(m){
console.log(m);
})
process.send({"message":"hello I am child"})
父子进程之间会创建IPC通道,message事件和send()便利用IPC通道通信.
句柄传递
学会如何创建子进程后,我们创建一个HTTP服务并启动多个进程来共同
做到充分利用CPU多核。
worker.js
var http = require('http');
http.createServer(function(req,res){
res.end('Hello,World');
//监听随机端口
}).listen(Math.round((1+Math.random())*1000),'127.0.0.1');
main.js
var fork = require('child_process').fork;
var cpus = require('os').cpus();
for(var i=0;i<cpus.length;i++){
fork('./worker.js');
}
上述代码会根据你的cpu核数来创建对应数量的fork进程,每个进程监听一个随机端口来提供HTTP服务。
上述就完成了一个典型的Master-Worker主从复制模式。在分布式应用中用于并行处理业务,具备良好的收缩性和稳定性。这里需要注意,fork一个进程代价是昂贵的,node单进程事件驱动具有很好的性能。此例的多个fork进程是为了充分利用CPU的核,并非解决并发问题.
上述示例有个不太好的地方就是占有了太多端口,那么能不能对于多个子进程全部使用同一个端口从而对外提供http服务也只是使用这一个端口。尝试将上述的端口随机数改为8080,启动会发现抛出如下异常。
events.js:72
throw er;//Unhandled 'error' event
Error:listen EADDRINUSE
XXXX
抛出端口被占有的异常,这意味着只有一个worker.js才能监听8080端口,而其余的会抛出异常。
如果要解决对外提供一个端口的问题,可以参考nginx反向代理的做法。对于Master进程使用80端口对外提供服务,而对于fork的进程则使用随机端口,Master进程接受到请求就将其转发到fork进程中
对于刚刚所说的代理模式,由于进程每收到一个连接会使用掉一个文件描述符,因此代理模式中客户端连接到代理进程,代理进程再去连接fork进程会使用掉两个文件描述符,OS中文件描述符是有限的,为了解决这个问题,node引入进程间发送句柄的功能。
在node的IPC进程通讯API中,send(message,[sendHandle])的第二个参数就是句柄。
句柄就是一种标识资源的引用,它的内部包含了指向对象的文件描述符。句柄可以用来描述一个socket对象,一个UDP套接子,一个管道
主进程向工作进程发送句柄意味着当主进程接收到客户端的socket请求后则直接将这个socket发送给工作进程,而不需要再与工作进程建立socket连接,则文件描述符的浪费即可解决。我们来看示例代码:
main.js
var cp = require('child_process');
var child = cp.fork('./child.js');
var server = require('net').createServer();
//监听客户端的连接
server.on('connection',function(socket){
socket.end('handled by parent');
});
//启动监听8080端口
server.listen(8080,function(){
//给子进程发送TCP服务器(句柄)
child.send('server',server);
});
child.js
process.on('message',function(m,server){
if(m==='server'){
server.on('connection',function(socket){
socket.end('handle by child');
});
}
});
使用telnet或curl都可以测试:
wang@wang ~/code/nodeStudy $ curl 192.168.10.104:8080
handled by parent
wang@wang ~/code/nodeStudy $ curl 192.168.10.104:8080
handle by child
wang@wang ~/code/nodeStudy $ curl 192.168.10.104:8080
handled by parent
wang@wang ~/code/nodeStudy $ curl 192.168.10.104:8080
handled by parent
测试结果是每次对于客户端的连接,有可能父进程处理也有可能被子进程处理。现在我们尝试仅提供http服务,并且为了让父进程更加轻量,仅让父进程传递句柄给子进程而不做请求处理:
main.js
var cp = require('child_process');
var child1 = cp.fork('./child.js');
var child2 = cp.fork('./child.js');
var child3 = cp.fork('./child.js');
var child4 = cp.fork('./child.js');
var server = require('net').createServer();
//父进程将接收到的请求分发给子进程
server.listen(8080,function(){
child1.send('server',server);
child2.send('server',server);
child3.send('server',server);
child4.send('server',server);
//发送完句柄后关闭监听
server.close();
});
child.js
var http = require('http');
var serverInChild = http.createServer(function(req,res){
res.end('I am child.Id:'+process.pid);
});
//子进程收到父进程传递的句柄(即客户端与服务器的socket连接对象)
process.on('message',function(m,serverInParent){
if(m==='server'){
//处理与客户端的连接
serverInParent.on('connection',function(socket){
//交给http服务来处理
serverInChild.emit('connection',socket);
});
}
});
当运行上述代码,此时查看8080端口占有会有如下结果:
wang@wang ~/code/nodeStudy $ lsof -i:8080
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
node 5120 wang 11u IPv6 44561 0t0 TCP *:http-alt (LISTEN)
node 5126 wang 11u IPv6 44561 0t0 TCP *:http-alt (LISTEN)
node 5127 wang 11u IPv6 44561 0t0 TCP *:http-alt (LISTEN)
node 5133 wang 11u IPv6 44561 0t0 TCP *:http-alt (LISTEN)
运行curl查看结果:
wang@wang ~/code/nodeStudy $ curl 192.168.10.104:8080
I am child.Id:5127
wang@wang ~/code/nodeStudy $ curl 192.168.10.104:8080
I am child.Id:5133
wang@wang ~/code/nodeStudy $ curl 192.168.10.104:8080
I am child.Id:5120
wang@wang ~/code/nodeStudy $ curl 192.168.10.104:8080
I am child.Id:5126
wang@wang ~/code/nodeStudy $ curl 192.168.10.104:8080
I am child.Id:5133
wang@wang ~/code/nodeStudy $ curl 192.168.10.104:8080
I am child.Id:5126
node child_process模块的更多相关文章
- node.js(七) 子进程 child_process模块
众所周知node.js是基于单线程模型架构,这样的设计可以带来高效的CPU利用率,但是无法却利用多个核心的CPU,为了解决这个问题,node.js提供了child_process模块,通过多进程来实现 ...
- node之子线程child_process模块
node.js是基于单线程模型架构,这样的设计可以带来高效的CPU利用率,但是无法却利用多个核心的CPU,为了解决这个问题,node.js提供了child_process模块,用于新建子进程,子进程的 ...
- nodejs(二)child_process模块
1.child_process是Node.js的一个十分重要的模块,通过它可以实现创建多进程,以利用多核计算资源. child_process模块提供了四个创建子进程的函数,分别是spawn,exec ...
- nodejs中的子进程,深入解析child_process模块和cluster模块
Node.js的进程管理 node遵循的是单线程单进程的模式,node的单线程是指js的引擎只有一个实例,且在nodejs的主线程中执行,同时node以事件驱动的方式处理IO等异步操作.node的 ...
- Node child_process Study.2
child_process 模块用于新建子进程.子进程的运行结果存储在系统缓存之中,等到子进程运行结束之后,主进程再用回调函数读取子进程的运行结果 1.exec() exec 方法用于执行base命令 ...
- 深入浅出node(2) 模块机制
这部分主要总结深入浅出Node.js的第二章 一)CommonJs 1.1CommonJs模块定义 二)Node的模块实现 2.1模块分类 2.2 路径分析和文件定位 2.2.1 路径分析 2.2.2 ...
- Node.js模块
每一个Node.js都是一个Node.js模块,包括JavaScript文件(.js).JSON文本文件(.json)和二进制模块文件(.node). mymodul.js function Hell ...
- 如何发布一个自定义Node.js模块到NPM(详细步骤)
咱们闲话不多说,直接开始! 由于我从没有使用过MAC,所以我不保证本文中介绍的操作与MAC一致. 文章开始我先假定各位已经在window全局安装了Node.js,下面开始进行详细步骤介绍: 本文本着, ...
- 编写原生Node.js模块
导语:当Javascript的性能需要优化,或者需要增强Javascript能力的时候,就需要依赖native模块来实现了. 应用场景 日常工作中,我们经常需要将原生的Node.js模块做为依赖并在项 ...
随机推荐
- PAT乙级1030
1030 完美数列 (25 分) 给定一个正整数数列,和正整数 p,设这个数列中的最大值是 M,最小值是 m,如果 M≤mp,则称这个数列是完美数列. 现在给定参数 p 和一些正整数,请你从中选择 ...
- Jmeter新手频犯错误之一(登录)
昨天被人问了一个问题:为什么我用Jmeter先创建一个登录请求,然后创建一个操作(比如计算账单)请求,运行之后结果树中却是status_code=401(即登录失败),我明明登录了啊.... emmm ...
- Centos7安装elasticsearch、logstash、kibana、elasticsearch head
环境:Centos7, jdk1.8 安装logstash 1.下载logstash 地址:https://artifacts.elastic.co/downloads/logstash/logsta ...
- koa2学习笔记02 - 给koa2添加系统日志 —— node日志管理模块log4js
前言 没有日志系统的后台应用是没有灵魂的, 平时工作中每次我们遇到接口报错的时候, 都会叫后台的童鞋看下怎么回事, 这时后台的童鞋都会不慌不忙的打开一个骚骚的黑窗口. 一串噼里啪啦的命令输进去, 哐哐 ...
- 有关ajax
1.什么是ajax? ajax是前端与后端交互所依赖的一项技术,它相当于一座桥梁,沟通了前端与后端. 2.ajax的优点 可以局部更新网页内容. 3.ajax的本质就是xmlHttpRequest对象 ...
- HTML5基础知识总结(一)
新增的标签和属性 1.结构标签 article section aside nav header footer hgroup figure address 2.媒体标签 video audio emb ...
- 用脚本js把结果转化为固定小数位的形式
function roundTo(base,precision) { var m=Math.pow(10,precision); var a=Math.round(base * m) / m; ret ...
- python 集合总结
''' 集合:1:他是无序的,他是不重复的. 2,他里面的元素必须是可哈希的. int str bool ()但是它本身是不可哈希的. 3,集合不能更改里面的元素. 4,集合可以求交集,并集,差集,反 ...
- 通过R语言统计考研英语(二)单词出现频率
通过R语言统计考研英语(二)单词出现频率 大家对英语考试并不陌生,首先是背单词,就是所谓的高频词汇.厚厚的一本单词,真的看的头大.最近结合自己刚学的R语言,为年底的考研做准备,想统计一下最近考研英语( ...
- 用NI的数据采集卡实现简单电子测试之1——USB-6009简介
本文从本人的163博客搬迁至此. 几年以来,一直担任学校“虚拟仪器”课程教师.以前上课都以介绍LabVIEW编程为主,硬件实验一直没有开展.这次借“西部高校实力提升工程”的机会,学院采购了一批NI的数 ...