在正文之前,我想问大家一个问题:
问:亲,你有基础吗? 答: 有啊,你说前端吗? 不就是HTML,JS,CSS 吗? so easy~ 问: oh-my-zsh... 好吧,那问题来了,挖掘机技术哪家强... 开玩笑。
现在才是问题的正内容。
其实,说多了都是泪,这些都是程序员的基本素质呀。。。 面tencent的时候,被一个总监,骂的阿弥陀佛么么哒. 今天在这里和大家分享一下,我的血泪史。
TCP内容
工欲善其事,必先利其器
一个程序员境界的提升,并不在于你写的一首好代码,更在于你能说出代码背后的故事。ok~ 鸡汤灌完了。我们开始说方法了。
首先这幅图大家必须记得非常清楚才行。
对了还有,
OSI七层模型大家应该烂熟于心的。
其中TCP处理transport层,主要是用来建立可靠的连接。 而建立连接的基础,是他丰富的报文内容(md~超级多).我们先来解释一下。 首先,我们TCP3次握手用的报文就是绿色的"TCP Flags"内容。 通过发送ACK,SYN包实现。具体涉及的Tag详见:
恩,基本的TCP内容,大家应该掌握了吧。OK, go on.
还是一样, 先上张图,让大家先看一下。 上面大家已经基本了解了TCP里面相应的字段,现在看看图里面的是不是觉得有些亲切嘞?
其实,大家看上面的图,差不多都已经能够摸清楚,每次发送请求的内容。其实,TCP3次握手是为了建立 稳定可靠的连接。所以也就不存在神马 2次连接等的怪癖。
(图中flag说明:SYN包表示标志位syn=1,ACK包表示标志位ack=1,SYN+ACK包表示标志位syn=1,ack=1)
现在,我们来正式进入3次握手环节。
并且 将新头部的AckNumber变为(y+1).然后发送给服务器,完成TCP3次连接。此时服务器和客户端都进入ESTABLISHED状态.
回答一下这个比较尴尬的问题,为什么只有3次握手,而不是4次,或者2次? 很简单呀,因为3次就够了,干嘛用4次。23333. 举个例子吧,假如是2次的话, 可能会出现这样一个情况。
TCP4次挥手,是比较简单的。大家对照上面那个图,我们一步一步进行一下讲解。
明白了吗?
大哥~ 等等,什么是2MSL呀~
哦,对哦。 这个还么说...
2MSL=2*MSL. 而MSL其实就是Maximum Segment Lifetime,中文意思就是报文最大生存时间。RFC 793中规定MSL为2分钟,实际应用中常用的是30秒,1分钟和2分钟等。 同样上面的TIME_WAT状态其实也就是2MSL状态。 如果超过改时间,则会将该报文废弃,然后直接进入CLOSED状态.
亲,请问php是一门什么语言? (提示,关于进程) 官方回答: php是一门基于多线程的语言 亲,请问nodeJS是一门什么语言?(提示,关于线程) 官方回答: Node.js是单线程!异步!非阻塞!(不过早已可以实现多进程交互了) 那php和nodeJS区别在哪呢?具体可以见图:
PHP
ok~ 简单吧。
亲,那进程和线程区别是什么嘞?
go die /(ㄒoㄒ)/~~
这算是计算机的基本知识吧。 首先我们需要记住的是,进程包括线程。这非常重要。
进程就是系统分配资源的基本单位(比如CPU,内存等)
线程就是程序执行的最小单位
进程有自己的空间,如果一个进程崩溃不会引起其它进程的崩溃。
线程,没有自己独立的空间,多个线程共享的是进程的地址空间,当然处理一些基本的如程序计数器,一组寄存器和栈等。
如果一个线程崩溃,它所在的进程就崩溃了。 虽然说,多进程很稳定,但是进程切换时,耗费的资源也是很大的。 所以对于大并发的nodeJS来说,使用多线程的效果要远远比多进程快,稳定。
线程的优势
1、系统在启动一个进程的时候,会首先在资源中独立一块出来,在后台建立一些列表进行维护。 而,线程是比进程低一个level的,所以创建线程所耗费的资源要远远比,创建进程的资源少。
总线程数<= CPU数量:并行运行 总线程数> CPU数量:并发运行
并行指的是,当你的CPU核数比线程数多的话,则会将每个线程都分在一个CPU核里进行处理。
并发指的是,当你的CPU核数比线程数少的话,则会利用“时间片轮转进程调度算法”,对每个线程进行同等的运行。
2、细化进程的处理,通常一个进程可以拆分为多个线程进行处理,就和模块化处理是类似的,使用模块化书写的效果要远远比使用单main入口方式书写 清晰,稳定。
并发,并行原理
亲, 并发和并行有什么共同点吗?
恩~ 有的, 他们都有个‘并’子,字面上看起来都是同时执行的意思。
没错,当然只是字面上而已。
实际上,并发和并行是完全不同的概念。 这里主要和CPU核数有关。这里为了理解,拿线程来作为参考吧。
当你的
总线程数<= CPU数量:并行运行 总线程数> CPU数量:并发运行
很明显,并行其实是真正意义上的同时执行。 当线程数< CPU核数时,每个线程会独立分配到一个CPU里进行处理。
大家看过火影忍者吗?
没错,就是鸣人 出关 口遁九尾之后。 他使用影分身,跑去各地支援同伴,对抗斑。 这里类比来说,就可以理解为, 每个CPU 都是鸣人的一个影分身,他们执行这各自不同的工作,但是,在同一时间上,他们都在运行。 这就是并行。
那并发嘞?
其实,并发有点难以理解,他做的工作其实,就是利用一系列算法实现,并行做的事。一个比较容易理解的就是“时间片轮转进程调度算法”。
即: 在系统控制下,每个线程轮流使用CPU,而且,每个线程使用时间必须很短(比如10ms), 所以这样切换下来。我们(愚蠢的人类,哈哈哈), 天真的以为任务,真的是在"并行"执行.
nodeJS的进程实现
一开始nodeJS最令人诟病的就是他的单线程特性。既是绝招也是死穴,不过nodeJS发展很快,在v0.8版本就已经添加了cluster作为内置模块,实现多核的利用。
关于nodeJS的进程模块,最主要的当然还是cluster. 通过调用child_process.fork()函数来开启进程。 先看一个具体的demo(from 官网)
var cluster = require('cluster'); var http = require('http'); var numCPUs = require('os').cpus().length; if (cluster.isMaster) { console.log("master start..."); // Fork workers. for (var i = 0; i < numCPUs; i++) { cluster.fork(); } //用来监听子worker创建监听服务 cluster.on('listening',function(worker,address){ console.log('listening: worker ' + worker.process.pid +', Address: '+address.address+":"+address.port); }); cluster.on('exit', function(worker, code, signal) { console.log('worker ' + worker.process.pid + ' died'); }); } else { http.createServer(function(req, res) { res.writeHead(200); res.end("hello world\n"); }).listen(0); }
存放为app.js 然后运行node app.js就可以实现一个简单的多进程效果。
结果可能为下:
master start... listening: worker 1559, Address: null:57803 listening: worker 1556, Address: null:57803 listening: worker 1558, Address: null:57803 listening: worker 1557, Address: null:57803
可以从上面的demo中看出,通过cluster.isMaster来区分master和worker. 而master和worker之间使用listen(0)进行通信.
当然你也可以手动打开一个端口共享监听。像这样.
http.createServer(function(req, res) { res.writeHead(200); res.end("hello world\n"); }).listen(3000);
cluster对应API
cluster对象的属性和函数
通过cluster.worker获得的worker对象和相应的参数
这些就是cluster的全部内容。不过这仅仅只是内容而已,如果使用cluster,这便是我们程序员要做的事了。
进程通信
由于nodeJS 只能实现单进程的效果,所以他的进程数只能为一个,但是通过引用cluster模块,可以开启多个子进程实现CPU的利用。
简单进程交互点击预览
运行后的结果为:
[master] start master... [master] fork: worker1 [master] fork: worker2 [master] fork: worker3 [master] fork: worker4 [master] online: worker1 [master] online: worker4 [master] online: worker2 [master] online: worker3 [worker] start worker ...1 [worker] start worker ...4 [worker] start worker ...2 [master] listening: worker4,pid:990, Address:null:3000 [master] listening: worker1,pid:987, Address:null:3000 [master] listening: worker2,pid:988, Address:null:3000 [worker] start worker ...3 [master] listening: worker3,pid:989, Address:null:3000
参照注释代码和上述的结果,我们可以很容易的得到一个触发逻辑。
运行过程是:
上面只是创建满负载子进程的流程。 但怎样实现进程间的交互呢? 很简单,master和worker监听message事件,通过传递参数,进行交互。
这个是多进程之间的通信
communication点击预览
我们来分解一下代码块:
//开启master监听worker的通信 cluster.workers[id].on('message', function(msg){ //... }); //开启worker监听master的通信 process.on('message', function(msg) { //... });
运行上面的demo. 这里就不细说,整个流程,只看一下信息通信这一块了。
nodeJS负载均衡
现在,nodeJS负载均衡应该是最容易实现的,其内部已经帮我们封装好了,我们直接调用就over了。
其中,实现负载均衡的模块就是cluster。以前cluster确实很累赘。负载均衡的算法实现的不是很好,导致的下场就是npm2的兴起。不过现在已经实现了负载均衡,官方说法就是用round-robin,来进行请求分配。 round-robin其实就是一个队列的循环,灰常容易理解。先看一下,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); }
(哥哥,你骗人,这哪里实现了负载均衡,这不就是上面的算法么?)
是呀,,, 我又没说负载均衡不是这个。
负载均衡就是帮你解决请求的分配问题。ok~ 为了证明,我没有骗你,我们来进行测试一下。
使用brew安装siege测试,当然你也可以使用其他测试工具,不过在MAC 上面最好使用siege和webbench或者ab,我这里使用siege
brew install siege
使用的测试语法就是
siege -c 并发数 -t 运行测试时间 URL
测试的时间后面需要带上单位,比如s,m,h,d等。默认单位是m(分钟). 举个例子吧.
siege -c 100 -t 10s http://girls.hustonline.net
对女生节网页进行 100次并发测试,持续时间是10s.
当然siege里还有其他的参数.
siege常用的就是这几个. 通常我们是搭配 -c + -r 或者-c + -t.
OK,现在我们开始我们的测试 procedure.
首先开启多进程NodeJS. node app.js
使用siege -c 100 -t 10s 127.0.0.1:3000. (Ps: 当然也可以使用http://localhost:3000进行代替)
得到的结果为
Transactions: 600 hits Availability: 100.00 % Elapsed time: 6.08 secs Data transferred: 0.01 MB Response time: 0.01 secs Transaction rate: 98.68 trans/sec Throughput: 0.00 MB/sec Concurrency: 0.88 Successful transactions: 600 Failed transactions: 0 Longest transaction: 0.04 Shortest transaction: 0.00
在10s内,发起了600次请求,最大的峰值是98.68 trans/sec。 通过统计分析,得到每个worker的分发量.
worker1:162 worker2:161 worker3:167 worker4:170
可以看出,基本上每个负载上分配的请求的数目都差不多。这就已经达到了负载均衡的效果。
本文分享自 交互设计前端开发与后端程序设计 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!