须要了解的基础概念 一个应用程序中,至少包含一个进程,一个进程至少包含一个线程。前端
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位
线程(Thread)是操做系统可以进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运做单位。
Node 的特色:node
主线程是单进程(后面版本出现了线程概念,开销较大);
基于事件驱动,异步非阻塞 I/O;
可用于高并发场景。
nodejs 原有版本中没有实现多线程,为了充分利用多核 cpu,能够使用子进程实现内核的负载均衡。c++
node 须要解决的问题:git
node 作耗时的计算时候,形成阻塞。
node 如何开启子进程
开发过程当中如何实现进程守护
概念太多,咱们从具体案例入手,看看单线程到底会带来什么问题。github
单线程的缺点
// file: question.js
const http = require('http');
http.createServer((req, res) => {
if (req.url === '/sum') { // 求和shell
var endTime = new Date().getTime() + 10000 while (new Date().getTime() < endTime) {} res.end('sum')
} else {npm
res.end('end');
}
}).listen(3000);
操做步骤后端
node question.js
打开浏览器,在一个 tab1 上访问 /sum 。快速打开另外一个 tab2,访问 / 。
请问会出现什么现象? 咱们发现 tab1 在转圈, tab2 也在转圈,这个现象就很奇怪了。tab1 在转圈咱们能够理解,由于咱们须要花费是 10s,可是 tab2 也须要 10s 后,才能被访问。这就很奇怪了。浏览器
这个问题就至关于,别人访问这个浏览器阻塞了 10s,你也要跟着阻塞 10s。这个问题就很难被接受了。所以得出结论,node 不太适合作 cpu 密集型的服务。服务器
如何解决这个问题?
为了解决这个问题,咱们引入子进程。
file: calc.js
var endTime = new Date().getTime() + 10000
while (new Date().getTime() < endTime) {}
process.send({
time: new Date().getTime()+''
});
改造 question.js
file: question.js
const http = require('http');
const {fork} = require('child_process');
const path = require('path');
http.createServer((req, res) => {
if (req.url === '/sum') { // 求和
// var endTime = new Date().getTime() + 10000 // while (new Date().getTime() < endTime) {} // res.end('sum') let childProcess = fork('calc.js', { cwd: path.resolve(__dirname) }); childProcess.on('message', function (data) { res.end(data.time + ''); })
} else {
res.end('end');
}
}).listen(3001);
从新启动 node question.js,发现 tab2,就不会阻塞了。
总结:node 做为服务器的话,须要开启子进程来解决 cpu 密集型的操做。以防止主线程被阻塞
子进程的使用 (child_process)
使用的方法
spawn 异步生成子进程
fork 产生一个新的 Node.js 进程,并使用创建的 IPC 通讯通道调用指定的模块,该通道容许在父级和子级之间发送消息。
exec 产生一个 shell 并在该 shell 中运行命令
execFile 无需产生 shell
spawn
spawn 产卵,能够经过此方法建立一个子进程
let { spawn } = require("child_process");
let path = require("path");
// 经过node命令执行sub_process.js文件
let childProcess = spawn("node",['sub_process.js'], {
cwd: path.resolve(__dirname, "test"), // 找文件的目录是test目录下
stdio: [0, 1, 2]
});
// 监控错误
childProcess.on("error", function(err) {
console.log(err);
});
// 监听关闭事件
childProcess.on("close", function() {
console.log("close");
});
// 监听退出事件
childProcess.on("exit", function() {
console.log("exit");
});
stdio 这个属性很是有特点,这里咱们给了 0,1,2 那么分别表明什么呢? stdio
0,1,2 分别对应当前主进程的 process.stdin,process.stdout,process.stderr,意味着主进程和子进程共享标准输入和输出
let childProcess = spawn("node",['sub_process.js'], {
cwd: path.resolve(__dirname, "test"), // 找文件的目录是test目录下
stdio: [0, 1, 2]
});
能够在当前进程下打印 sub_process.js 执行结果
默认不提供 stdio 参数时,默认值为 stdio:['pipe'],也就是只能经过流的方式实现进程之间的通讯
let { spawn } = require("child_process");
let path = require("path");
// 经过node命令执行sub_process.js文件
let childProcess = spawn("node",['sub_process.js'], {
cwd: path.resolve(__dirname, "test"),
stdio:['pipe'] // 经过流的方式
});
// 子进程读取写入的数据
childProcess.stdout.on('data',function(data){
console.log(data);
});
// 子进程像标准输出中写入
process.stdout.write('hello');
使用 ipc 方式通讯,设置值为 stdio:['pipe','pipe','pipe','ipc'],能够经过 on('message')和 send 方法进行通讯
let { spawn } = require("child_process");
let path = require("path");
// 经过node命令执行sub_process.js文件
let childProcess = spawn("node",['sub_process.js'], {
cwd: path.resolve(__dirname, "test"), stdio:['pipe','pipe','pipe','ipc'] // 经过流的方式
});
// 监听消息
childProcess.on('message',function(data){
console.log(data);
});
// 发送消息
process.send('hello');
还能够传入ignore 进行忽略 , 传入inherit表示默认共享父进程的标准输入和输出
产生独立进程
let { spawn } = require("child_process");
let path = require("path");
// 经过node命令执行sub_process.js文件
let child = spawn('node',['sub_process.js'],{
cwd:path.resolve(__dirname,'test'), stdio: 'ignore', detached:true // 独立的线程
});
child.unref(); // 放弃控制
做用:开启线程后,而且放弃对线程的控制。咱们就能够不占用控制太后台运行了。
fork
衍生新的进程,默认就能够经过ipc方式进行通讯
let { fork } = require("child_process");
let path = require("path");
// 经过node命令执行sub_process.js文件
let childProcess = fork('sub_process.js', {
cwd: path.resolve(__dirname, "test"),
});
childProcess.on('message',function(data){
console.log(data);
});
fork是基于spawn的,能够多传入一个silent属性, 设置是否共享输入和输出
fork原理
function fork(filename,options){
let stdio = ['inherit','inherit','inherit'] if(options.silent){ // 若是是安静的 就忽略子进程的输入和输出 stdio = ['ignore','ignore','ignore'] } stdio.push('ipc'); // 默认支持ipc的方式 options.stdio = stdio return spawn('node',[filename],options)
}
execFile
经过node命令,直接执行某个文件
let childProcess = execFile("node",['./test/sub_process'],function(err,stdout,stdin){
console.log(stdout);
});
内部调用的是spawn方法
exec
let childProcess = exec("node './test/sub_process'",function(err,stdout,stdin){
console.log(stdout)
});
内部调用的是execFile,其实以上的三个方法都是基于spawn的
实现集群
// file cluster.js 主线程
// 内部原理就是多进程
// 分布式 前端和后端 集群 多个功能相同的来分担工做
// 集群 就能够实现多个cpu的负载均衡 通常状况
// 不一样进程 监听同一个端口号
const {fork} = require('child_process');
const cpus = require('os').cpus().length;
const path = require('path');
// 如今主进程中先启动一个服务
const http = require('http');
let server = http.createServer(function (req,res) {
res.end(process.pid+' '+ ' main end')
}).listen(3000);
for(let i = 0 ; i < cpus-1 ; i++ ){
let cp = fork('server.js',{cwd:path.resolve(__dirname,'worker'),stdio:[0,1,2,'ipc']}); cp.send('server',server); // 我能够在ipc 模式下第二个参数传入一个http服务 或者tcp服务
}
// 多个请求都是i/o密集
// cluster 集群
// file worker/server.js 子进程
const http = require('http');
process.on('message',function (data,server) {
http.createServer(function (req,res) { res.end(process.pid+' '+ 'end') }).listen(server); // 多进程监控同一个端口号
})
// file http.get.js 请求脚本
const http = require('http');
for(let i =0 ; i < 10000;i++){
http.get({ port:3000, hostname:'localhost' },function (res) { res.on('data',function (data) { console.log(data.toString()) }) })
}
启动请求脚本之后,屡次发送请,能够清楚的发现请求的进程pid 不是同一个pid。
cluster模块实现集群
let cluster = require("cluster");
let http = require("http");
let cpus = require("os").cpus().length;
const workers = {};
if (cluster.isMaster) {
cluster.on('exit',function(worker){ console.log(worker.process.pid,'death') let w = cluster.fork(); workers[w.pid] = w; })
for (let i = 0; i < cpus; i++) {
let worker = cluster.fork(); workers[worker.pid] = worker;
}
} else {
http
.createServer((req, res) => { res.end(process.pid+'','pid'); }) .listen(3000);
console.log("server start",process.pid);
}
上诉的代码有点反人类,可是 c++ 中也是存在这样操做进程的。
另外一种方式
// file
const cluster = require('cluster');
const cpus = require('os').cpus();
// 入口文件
cluster.setupMaster({
exec: require('path').resolve(__dirname,'worker/cluster.js'),
});
cluster.on('exit',function (worker) {
console.log(worker.process.pid); cluster.fork(); // 在开启个进程
})
for(let i = 0; i < cpus.length ;i++){
cluster.fork(); // child_process fork 会以当前文件建立子进程 // 而且isMaster 为false 此时就会执行else方法
}
// pm2 专门 开启 重启 直接采用集群的方式
// 模块
// node worker/cluster.js
// 咱们的项目逻辑不少
const http = require('http');
http.createServer((req, res) => {
if (Math.random() > 0.5) { SDSADADSSA(); } // 在集群的环境下能够监听同一个端口号 res.end(process.pid + ':' + 'end')
}).listen(3000);
pm2应用
pm2能够把你的应用部署到服务器全部的CPU上,实现了多进程管理、监控、及负载均衡
安装pm2
npm install pm2 -g # 安装pm2
pm2 start server.js --watch -i max # 启动进程
pm2 list # 显示进程状态
pm2 kill # 杀死所有进程
pm2 start npm -- run dev # 启动npm脚本
pm2配置文件
pm2 ecosystem
配置项目自动部署
module.exports = {
apps : [{
name: 'my-project', script: 'server.js', // Options reference: https://pm2.io/doc/en/runtime/reference/ecosystem-file/ args: 'one two', instances: 2, autorestart: true, watch: false, max_memory_restart: '1G', env: { NODE_ENV: 'development' }, env_production: { NODE_ENV: 'production' }
}],
deploy : {
production : { user : 'root', host : '39.106.14.146', ref : 'origin/master', repo : 'https://github.com/wakeupmypig/pm2-deploy.git', path : '/home', 'post-deploy' : 'npm install && pm2 reload ecosystem.config.js --env production' }
}
};
pm2 deploy ecosystem.config.js production setup # 执行git clone
pm2 deploy ecosystem.config.js production # 启动pm2
文章来源:Biaofun标梵互动(https://www.biaofun.com/)