关于node.js:山东标梵简单分析下-Nodejs-关于集群的那些事

19次阅读

共计 6989 个字符,预计需要花费 18 分钟才能阅读完成。

须要理解的根底概念 一个应用程序中,至多蕴含一个过程,一个过程至多蕴含一个线程。

过程(Process)是计算机中的程序对于某数据汇合上的一次运行流动,是零碎进行资源分配和调度的根本单位
线程(Thread)是操作系统可能进行运算调度的最小单位。它被蕴含在过程之中,是过程中的理论运作单位。
Node 的特点:

主线程是单过程 (前面版本呈现了线程概念,开销较大);
基于事件驱动,异步非阻塞 I/O;
可用于高并发场景。
nodejs 原有版本中没有实现多线程,为了充分利用多核 cpu, 能够应用子过程实现内核的负载平衡。

node 须要解决的问题:

node 做耗时的计算时候,造成阻塞。
node 如何开启子过程
开发过程中如何实现过程守护
概念太多,咱们从具体案例动手,看看单线程到底会带来什么问题。

单线程的毛病
// file: question.js
const http = require(‘http’);
http.createServer((req, res) => {
if (req.url === ‘/sum’) {// 求和

var endTime = new Date().getTime() + 10000
while (new Date().getTime() < endTime) {}
res.end('sum')

} else {

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/)

正文完
 0