首先郑重声明:
nodeJS 是一门单线程!异步!非阻塞语言!
nodeJS 是一门单线程!异步!非阻塞语言!
nodeJS 是一门单线程!异步!非阻塞语言!php
重要的事情说3遍。 由于nodeJS天生自带buff, 因此从一出生就受到 万千 粉丝的追捧(俺,也是它的死忠). 可是,傻逼php 居然嘲笑 我大NodeJS 的性能。 说不稳定,不可靠,只能利用单核CPU。 辣鸡 nodeJS.
艹!艹!艹!
搞mo shi~
但,大哥就是大哥,nodeJS在v0.8 的时候就已经加入了cluster的模块。 彻底打脸php. 虽然,如今php 也开始抄袭nodeJS, 退出php7, 可是,渣渣,你就只会抄...
233333
对不起啊,上面是我自已意淫的一段~ 以上内容,纯属调侃,若是雷同,纯属巧合。
Ok~ 咱们来正式介绍一下nodeJS的多进程吧~html
之前,因为cluster 自己的不完善,可能因为多方面缘由吧,实现性能很差。 结果是,pm2 包的 崛起。 轻松使用一个pm2 就能够开启多进程,实现负载均衡的效果。前端
pm2 start app.js
pm2的内部和cluster内部实现实际上是一个道理,都是封装了一层child_process--fork. 而child_process--fork 则是封装了unix 系统的fork 方法。 既然,都到这了,咱们来看看官方给出的解释吧。node
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.web
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进程 管理。
看图~~~apache
其实就是这个意思。
Ok~ 这只是系统建立子进程的模型。那么在NodeJs中是怎样实现进程之间的交互的呢?
很简单监听端口呗。。。
可是,实现通讯不是很难,关键在于若是分配请求,这一点nodeJS 踩的坑确实很大。npm
long time agosegmentfault
nodeJS的master 开始并非上帝, 他只是一个小小的太监,每次请求(妃子)来的时候,他只会默默的看着几个worker小皇帝相互争夺,若是某个worker胜出,则其余的worker也就草草了事,等下一个请求过来。因此说,每来一次请求,都会引发一场腥风血雨。而,咱们体会最深的就是惊群现象,即,CPU爆表.
借用TJ大神的一幅图,说明一下。
这里,master只是绑定端口,而不会对来的请求作任何处理。 经过将socket的fd给fork出来的进程。形成的结果就是4我的男人(worker)抢一个妃子(request). 那场面别提有多血腥了。
前面说过,cluster其实就是对child_process的一层封装,那咱们继续往底层走一点。实现cluster多进程。 首先,咱们须要了解,这几个模块的基本用法。net,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
如今咱们已经学会了,如何使用child_process来建立一个基本的进程了。
关于net 这一模块,你们能够参考一下net模块.
ok . 如今咱们正式进入,模拟nodeJS cluster模块通讯的procedure了。
这里先介绍一下,曾经的cluster实现的一套机理。一样,再放一次图
咱们使用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 轮转方法。
如今具体的实现模型就变成这个.
由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说明我已经作了阐述了。咱们来看一下具体的实现吧
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),并发数,响应时间,吞吐量,吞吐率
自从咱们和服务器扯上关系后,咱们前端的性能测试真的不少。但这也是咱们必须掌握的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.
熟悉数据库操做的童鞋,应该知道,在数据库中经常会提到一个叫作事务的概念。 在数据库中,一个事务,经常表明着一个具体的处理流程和结果. 好比,我如今想要的数据是 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~ 咱们来实例分析一下吧.
首先,咱们拿到一份测试数据.
接着,咱们进行数据分析.
根据并发数和吞吐率的关系得出下列的图.
OK~ 咱们会发现从大约130并发数的地方开始,吞吐率开始降低,并且越多降低的越厉害。 主要是由于,在前面部分随着用户数的上升,空闲的系统资源获得充分的利用,固然就和正太曲线同样,总会有个顶点。 当到达必定值后,顶点就会出现了. 这就咱们的系统的一个瓶颈.
接着,咱们细化分析,响应时间和并发用户数的相关性
一样额道理,当并发数到达130左右,正对每一个req的响应时间开始增长,越大越抖,这适合吞吐率是相关的。 因此,咱们能够得出一个结论,该次链接 并发数 最好设置为100~150之间。 固然,这样的分析很肤浅,不过,对于咱们这些前端宝宝来讲了解一下就足够了。
接下来,咱们使用工具来武装本身的头脑.
这里主要介绍一个测试工具,siege.
事实上并发测试工具主要有3个siege,ab,还有webbench. 我这里之因此没介绍webbench的缘由,由于,我在尝试安装他时,老子,电脑差点就挂了(个人MAC pro)... 不事后面,被聪明的我 巧妙的挽回~ 因此,若是有其余大神在MAC x11 上成功安装,能够私信小弟。让我学习学习。
ok~ 吐槽完了。咱们正式说一下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,勉励写出更优质的文章吗?
转载请注明出处和做者:http://www.javashuo.com/article/p-nzwmqumv-x.html