nodeJS多历程

起首郑重声明:
nodeJS 是一门单线程!异步!非壅塞言语!
nodeJS 是一门单线程!异步!非壅塞言语!
nodeJS 是一门单线程!异步!非壅塞言语!

重要的事变说3遍。 因为nodeJS天生自带buff, 所以从一出生就遭到 万千 粉丝的追捧(俺,也是它的死忠). 然则,傻逼php 居然讪笑 我大NodeJS 的机能。 说不稳定,不可靠,只能应用单核CPU。 辣鸡 nodeJS.
艹!艹!艹!
搞mo shi~
但,老大就是老大,nodeJS在v0.8 的时刻就已加入了cluster的模块。 完全打脸php. 虽然,如今php 也最先剽窃nodeJS, 退出php7, 然则,渣渣,你就只会抄…
233333
对不起啊,上面是我自已意淫的一段~ 以上内容,纯属奚弄,如果相同,纯属巧合。
Ok~ 我们来正式引见一下nodeJS的多历程吧~

cluster的宿世此生

之前,因为cluster 自身的不完善,可以因为多方面缘由吧,完成机能不好。 效果是,pm2 包的 兴起。 轻松运用一个pm2 就可以开启多历程,完成负载平衡的效果。

pm2 start app.js

pm2的内部和cluster内部完成实际上是一个原理,都是封装了一层child_process–fork. 而child_process–fork 则是封装了unix 体系的fork 要领。 既然,都到这了,我们来看看官方给出的诠释吧。

fork() creates a new process by duplicating the calling process. The new process is referred to as the child process. The calling process is referred to as the parent process.

The child process and the parent process run in separate memory spaces. At the time of fork() both memory spaces have the same content. Memory writes, file mappings (mmap(2)), and unmappings (munmap(2)) performed by one of the processes do not affect the other.

俺来翻译一下,fork实在就是建立子历程的要领,新建立的历程被认为是子历程,而挪用fork的历程则是父历程。 子历程和父历程原本是在自力的内存空间中的。但当你运用了fork今后,二者就处在同一个作用域内了。 然则,内存的读写,文件的map,都不会影响对方。

上面那段的意义就是,你建立的历程实在可以互相通讯,而且被master历程 治理。
看图~~~
《nodeJS多历程》

实在就是这个意义。
Ok~ 这只是体系建立子历程的模子。那末在NodeJs中是怎样完成历程之间的交互的呢?
很简朴监听端口呗。。。
然则,完成通讯不是很难,关键在于如果分派要求,这一点nodeJS 踩的坑确切很大。

nodeJS 完成历程分派的黑汗青

long time ago

nodeJS的master 最先并非天主, 他只是一个小小的寺人,每次要求(妃子)来的时刻,他只会默默的看着几个worker小天子互相争取,如果某个worker胜出,则其他的worker也就敷衍了事,等下一个要求过来。所以说,每来一次要求,都邑引发一场凄风苦雨。而,我们体味最深的就是惊群征象,即,CPU爆表.
借用TJ大神的一幅图,申明一下。
《nodeJS多历程》
这里,master只是绑定端口,而不会对来的要求做任何处置惩罚。 经过过程将socket的fd给fork出来的历程。形成的效果就是4个人男子(worker)抢一个妃子(request). 那排场别提有多血腥了。
前面说过,cluster实在就是对child_process的一层封装,那我们继承往底层走一点。完成cluster多历程。 起首,我们须要相识,这几个模块的基础用法。net,child_process.

child_process

这个应当是nodeJS 历程最中心的模块。 基础的要领,有几个,不过我这里,只引见比较中心的:spawn ,fork ,exec。如果人人有兴致,可以去child_process参考.

  • child_process.spawn(command, args)

该要领用来运转指定的顺序。比方: node app.js.他是异步的敕令,但不支撑callback, 不过我们可以运用process.on来监听效果。 他自带3个参数.
command: 实行敕令
args[Array]: 敕令所带的参数
options[Object]: 环境变量对象

OK~ 我们举个一个简朴的demo: 试一试运转 touch apawn.js

const spawn = require('child_process').spawn;
const touch = spawn('touch',['spawn.js']);

touch.stdout.on('data', (data) => {
  console.log(`stdout: ${data}`);
});

touch.stderr.on('data', (data) => {
  console.log(`stderr: ${data}`);
});

touch.on('close', (code) => {
  console.log(`child process exited with code ${code}`);
});

如果,准确的话,应当会输
child process exited with code 0. 然后运转目次会天生pawn.js文件。 固然,如果你须要运转多参数的敕令的话这就有点蛋疼了。
所以,nodeJS 运用了exec对其举行很好的封装,而且他支撑回调函数,这比较可以让我们明白。

  • child_process.exec(order,cb(err[,stdout,stderr]));

order: 就是你实行的敕令. 比方: rm spawn.js
cb: 就是敕令实行胜利后的回调函数。

const childProcess = require('child_process');

const ls = childProcess.exec('rm spawn.js', function (error, stdout, stderr) {
   if (error) {
     console.log(error.stack);
     console.log('Error code: '+error.code);
   }
   console.log('Child Process STDOUT: '+stdout);
});

平常情况下会删除spawn.js文件。
上面两个只是简朴的运转历程的敕令。 末了,(Boss老是末了进场的). 我们来瞧瞧fork要领的运用.
fork实在也是用来实行历程,比方,spawn(“node”,[‘app.js’]),实在和fork(‘app.js’) 是一样的效果的。然则,fork牛逼的处所在于他在开启一个子历程时,同时建立了一个信息通道(双工的哦). 俩个历程之间运用process.on(“message”,fn)和process.send(…)举行信息的交换.

  • child_process.fork(order) //建立子历程

  • worker.on(‘message’,cb) //监听message事宜

  • worker.send(mes) //发送信息

他和spawn相似都是经过过程返回的通道举行通讯。举一个demo, 两个文件master.js和worker.js 来看一下.

//master.js
const childProcess = require('child_process');
const worker = childProcess.fork('worker.js');

worker.on('message',function(mes){
    console.log(`from worder, message: ${mes}`);
});
worker.send("this is master");

//worker.js
process.on('message',function(mes){
    console.log(`from master, message: ${mes}`);
});
process.send("this is worker");

运转,node app.js, 会输出一下效果:

from master, message: this is master
from worker, message: this is worker
  1. 如今我们已学会了,怎样运用child_process来建立一个基础的历程了。
    关于net 这一模块,人人可以参考一下net模块.

ok . 如今我们正式进入,模拟nodeJS cluster模块通讯的procedure了。

out of date 的cluster

这里先引见一下,曾的cluster完成的一套机理。一样,再放一次图
《nodeJS多历程》
我们运用net和child_process来模拟一下。

//master.js
const net = require('net');
const fork = require('child_process').fork;

var handle = net._createServerHandle('0.0.0.0', 3000);

for(var i=0;i<4;i++) {
   fork('./worker').send({}, handle);
}
//worker.js
const net = require('net');
//监听master发送过来的信息
process.on('message', function(m, handle) {
  start(handle);
});

var buf = 'hello nodejs'; ///返回信息
var res = ['HTTP/1.1 200 OK','content-length:'+buf.length].join('\r\n')+'\r\n\r\n'+buf;  //嵌套字

function start(server) {
    server.listen();
    var num=0;
    //监听connection函数
    server.onconnection = function(err,handle) {
        num++;
        console.log(`worker[${process.pid}]:${num}`);
        var socket = new net.Socket({
            handle: handle
        });
        socket.readable = socket.writable = true;
        socket.end(res);
    }
}

ok~ 我们运转一下顺序, 起首运转node master.js.
然后运用测试东西,siege.
siege -c 100 -r 2 http://localhost:3000
OK,我们看一下,究竟此时的负载是不是平衡。

worker[1182]:52
worker[1183]:42
worker[1184]:90
worker[1181]:16

发明,如许任由worker去争取要求,效力真的很低呀。每一次,触发要求,都有可以致使惊群事宜的发作啊喂。所以,厥后cluster转变了一种形式,运用master来控制要求的分派,官方给出的算法实在就是round-robin 轮转要领。

高富帅版cluster

如今详细的完成模子就变成这个.
《nodeJS多历程》
由master来控制要求的赋予。经过过程监听端口,建立一个socket,将取得的要求传递给子历程。
从tj大神那边自创的代码demo:

//master
const net = require('net');
const fork = require('child_process').fork;

var workers = [];
for (var i = 0; i < 4; i++) {
   workers.push(fork('./worker'));
}

var handle = net._createServerHandle('0.0.0.0', 3000);
handle.listen();
//将监听事宜移到master中
handle.onconnection = function (err,handle) {
    var worker = workers.pop();  //掏出一个pop
    worker.send({},handle);
    workers.unshift(worker);  //再放回掏出的pop
}


//worker.js
const net = require('net');
process.on('message', function (m, handle) {
  start(handle);
});

var buf = 'hello Node.js';
var res = ['HTTP/1.1 200 OK','content-length:'+buf.length].join('\r\n')+'\r\n\r\n'+buf;

function start(handle) {
    console.log('got a connection on worker, pid = %d', process.pid);
    var socket = new net.Socket({
        handle: handle
    });
    socket.readable = socket.writable = true;
    socket.end(res);
}

这里就经过master来掌控全局了. 当一个天子(worker)正在宠幸妃子的时刻,master就会部署剩下的几个天子列队一个几个的来。 实在中心的handle就会我们详细的营业逻辑. 犹如:app.js.
ok~ 我们再来看一下cluster模块完成多历程的详细写法.

cluster模块完成多历程

如今的cluster已可以说完全做到的负载平衡。在cluster申明我已做了论述了。我们来看一下详细的完成吧

var cluster = require('cluster');
var http = require('http');
var numCPUs = require('os').cpus().length;

if (cluster.isMaster) {
    console.log('[master] ' + "start master...");

    for (var i = 0; i < numCPUs; i++) {
         cluster.fork();
    }

    cluster.on('listening', function (worker, address) {
        console.log('[master] ' + 'listening: worker' + worker.id + ',pid:' + worker.process.pid + ', Address:' + address.address + ":" + address.port);
    });

} else if (cluster.isWorker) {
     console.log('[worker] ' + "start worker ..." + cluster.worker.id);
    var num = 0;
    http.createServer(function (req, res) {
        num++;
        console.log('worker'+cluster.worker.id+":"+num);
        res.end('worker'+cluster.worker.id+',PID:'+process.pid);
    }).listen(3000);
}

这里运用的是HTTP模块,固然,完全也可以替换为socket模块. 不过因为如许誊写,将集群和单边给殽杂了。 所以,引荐写法是将详细营业逻辑自力出来.

var cluster = require('cluster');
var numCPUs = require('os').cpus().length;

if (cluster.isMaster) {
    console.log('[master] ' + "start master...");

    for (var i = 0; i < numCPUs; i++) {
         cluster.fork();
    }

    cluster.on('listening', function (worker, address) {
        console.log('[master] ' + 'listening: worker' + worker.id + ',pid:' + worker.process.pid + ', Address:' + address.address + ":" + address.port);
    });

} else if (cluster.isWorker) {
    require('app.js');
}
//app.js就是开启详细的营业逻辑了

//app.js详细内容
const net = require('net');
//自动建立socket
const server = net.createServer(function(socket) { //'connection' listener
    socket.on('end', function() {
        console.log('server disconnected');
    });
    socket.on('data', function() {
        socket.end('hello\r\n');
    });
});
//开启端口的监听
server.listen(8124, function() { //'listening' listener
    console.log('working')
});

接着我们开启效劳,node master.js
然后举行测试
siege -c 100 -r 2 http://localhost:8124
我这里开启的是长衔接. 每一个worker处置惩罚的长衔接数是有限的。所以,当有分外的衔接到来时,worker会断开当前没有相应的衔接,去处置惩罚新的衔接。
不过,寻常我们都是运用HTTP开启 短衔接,疾速处置惩罚大并发的要求。
这是我改成HTTP短衔接今后的效果

Transactions:                 200 hits
Availability:              100.00 %
Elapsed time:                2.09 secs
Data transferred:            0.00 MB
Response time:                0.02 secs
Transaction rate:           95.69 trans/sec
Throughput:                0.00 MB/sec
Concurrency:                1.74
Successful transactions:         200
Failed transactions:               0
Longest transaction:            0.05
Shortest transaction:            0.02

那,怎样模拟大并发嘞?
e e e e e e e e e …
本身处理啊~
开顽笑的啦~ 不然我写blog是为了什么呢? 就是为了流传学问.
在引见东西之前,我想先说几个关于机能的基础观点
QPS(TPS),并发数,相应时候,吞吐量,吞吐率

你母鸡的机能测试theories

自从我们和效劳器扯上关联后,我们前端的机能测试真的许多。但这也是我们必需控制的tip. 原本前端宝宝只须要看看控制台,相识一下网页运转是不是运转顺畅, 看看TimeLine,Profile 就可以了。 不过,作为一位有寻求,有志于转变天下的童鞋来讲。。。
md~ 又要学了…
ok~ 好了,在进入正题之前,我再放一次 线上的测试效果.

Transactions:                 200 hits
Availability:              100.00 %
Elapsed time:               13.46 secs
Data transferred:            0.15 MB
Response time:                3.64 secs
Transaction rate:           14.86 trans/sec
Throughput:                0.01 MB/sec
Concurrency:               54.15
Successful transactions:         200
Failed transactions:               0
Longest transaction:           11.27
Shortest transaction:            0.01

依据上面的数据,就可以得出,你网页的大抵机能了。
恩~ let’s begin

吞吐率

关于吞吐率有多种解读,一种是:描写web效劳器单元时候处置惩罚要求的才。依据这个形貌,其单元就为: req/sec. 另一种是: 单元时候内收集上传输的数据量。 而依据这个形貌的话,他的单元就为: MB/sec.
而这个目标就是上面数据中的Throughput. 固然,肯定是越大越好了

吞吐量

这个和上面的吞吐率很有点关联的。 吞吐量是在没有时候的限定下,你一次测试的传输数据总和。 所以,没有时候前提的测试,都是耍流氓。
这个对应于上面数据中的Data transferred.

事件 && TPS

熟习数据库操纵的童鞋,应当晓得,在数据库中经常会提到一个叫做事件的观点。 在数据库中,一个事件,经常代表着一个详细的处置惩罚流程和效果. 比方,我如今想要的数据是 2013-2015年,数学期末考试成绩排名. 这个就是一个详细的事件,那末我们映射到数据库中就是,掏出2013-2015年的排名,然后取平均值,返回末了的排序效果。 可以看出,事件并不单单指单一的操纵,他是由一个或一个以上 操纵组合而成具有 实际意义的。 那,反应到前端测试,我们应当怎样去定义呢? 起首,我们须要相识,前端的收集交换实在就是 要求-相应形式. 也就是说,每一次要求,我们都可以明白为一次事件(trans).
所以,TPS(transaction per second)就可以明白为1sec内,体系可以处置惩罚的要求数量.他的单元也就是: trans/sec . 你固然也可以明白为seq/sec.
所以说,TPS 应当是权衡一个体系承载力最优的一个标识.
TPS的盘算公式很轻易的出来就是: Transactions / Elapsed time.
不过, 凡事无相对。 人人今后碰到测试的时刻,应当就会晓得的.

并发数

就是效劳器可以并发处置惩罚的衔接数,详细我也母鸡他的单元是什么。 官方给出的诠释是:

Concurrency is average number of simultaneous connections, a number which rises as server performance decreases.

这里我们就明白为,这就是一个权衡体系的承载力的一个规范吧。 当Concurrency 越高,示意 体系承载的越多,但机能也越低。

ok~ 然则我们怎样应用这些数据,来肯定我们的并发战略呢? e e e e e e e …
固然, 一两次测试的效果真的没有什么卵用. 所以实际上,我们须要举行屡次测试,然后绘图才行。 固然,一些大公司,早就有一套完全的体系来盘算你web效劳器的瓶颈,以及 给出 最优的并发战略.
空话不多说,我们来看看,怎样剖析,才得出 比较好的 并发战略。

探讨并发战略

起首,我们这里的并发须要举行辨别. 一个是并发的要求数,一个是并发的用户数. 这两个关于效劳器是完全差别的需求。
如果100个用户同时向效劳器离别举行10次要求,与1个用户向效劳器一连举行1000次要求。两个的效果一样么?

一个用户向效劳器一连举行1000次要求的过程当中,任何时刻效劳器的网卡吸收缓存区中只要来自该用户的1个要求,而100个用户同时向效劳器离别举行10次要求的过程当中,效劳器网卡吸收缓冲区中最多有100个守候处置惩罚的要求,明显这时刻效劳器的压力更大。

所以上面所说的 并发用户数和吞吐率 是完全不一样的.
不过平常来讲,我们更注重的是Concurrency(并发用户数). 因为如许更能反应出体系的 才。 平常,我们都邑对并发用户数举行一些限定,比方apache的maxClients参数.
ok~ 我们来实例剖析一下吧.

起首,我们拿到一份测试数据.
《nodeJS多历程》

接着,我们举行数据剖析.
依据并发数和吞吐率的关联得出以下的图.
《nodeJS多历程》
OK~ 我们会发明从约莫130并发数的处所最先,吞吐率最先下落,而且越多下落的越凶猛。 重要是因为,在前面部份跟着用户数的上升,余暇的体系资源获得充足的应用,固然就和正太曲线一样,总会有个极点。 当抵达肯定值后,极点就会涌现了. 这就我们的体系的一个瓶颈.
接着,我们细化剖析,相应时候和并发用户数的相干性
《nodeJS多历程》
一样额原理,当并发数抵达130摆布,正对每一个req的相应时候最先增添,越大越抖,这合适吞吐率是相干的。 所以,我们可以得出一个结论,该次衔接 并发数 最好设置为100~150之间。 固然,如许的剖析很浅薄,不过,关于我们这些前端宝宝来讲相识一下就足够了。

接下来,我们运用东西来武装本身的思想.
这里重要引见一个测试东西,siege.

并发测试东西

事实上并发测试东西重要有3个siege,ab,另有webbench. 我这里之所以没引见webbench的缘由,因为,我在尝试装置他时,老子,电脑差点就挂了(我的MAC pro)… 不过背面,被智慧的我 奇妙的挽回~ 所以,如果有其他大神在MAC x11 上胜利装置,可以私信小弟。让我进修进修。
ok~ 吐槽完了。我们正式说一下siege吧

siege

装置siege应用MAC神器 homebrew, 就是就和js前端天下的npm一样.
装置ing:
brew install siege
装置胜利–bingo
接着,我们来看一下语法吧.

  • -c NUM 设置并发的用户数量.eg: -c 100;

  • -r NUM 设置发送几轮的要求,即,总的要求数为: -cNum*-rNum然则, -r不能和-t一同运用(为何呢?你猜).eg: -r 20

  • -t NUM 测试持续时候,指你运转一次测试须要的时候,在timeout后,完毕测试.

  • -f file. 用来测试file内里的url途径 eg: -f girls.txt.

  • -b . 就是讯问开不开启基准测试(benchmark)。 这个参数不太重要,有兴致的同砚,可以下去进修一下。

关于-c -r我就不引见了。 人人有兴致,可以参考一下,我前一篇文章让你晋级的收集学问. 这里重要引见一下 -f 参数.
平常,如果我们想要测试多个页面的话,可以新建一个文件,在文件中建立 你想测试的一切网页地点.
比方:
//文件名为 urls.txt

www.example.com
www.example.org
123.45.67.89

然后运转测试
siege -f your/file/path.txt -c 100 -t 10s
OK~ 关于历程和测试的内容就引见到这了。

如果人人以为,嘿, 这哥们写的文章不错呀~
能请我喝杯coffee,鼓励写出更优良的文章吗?
《nodeJS多历程》

转载请说明出处和作者:https://segmentfault.com/a/1190000004621734

    原文作者:villainhr
    原文地址: https://segmentfault.com/a/1190000004621734
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞