在正文以前,我想問你們一個問題:
問:親,你有基礎嗎?
答: 有啊,你說前端嗎? 不就是HTML,JS,CSS 嗎? so easy~
問: oh-my-zsh... 好吧,那問題來了,挖掘機技術哪家強... 開玩笑。
如今纔是問題的正內容。php
你知道TCP的基本內容嗎?(母雞啊~)前端
好吧,那你知道TCP的3次握手,4次揮手嗎?(知道一點點)node
恩,好,那什麼是進程呢?什麼是線程呢?(母雞啊。。)程序員
那併發和並行又是什麼呢?(母雞啊)web
OMG, 那nodeJS多進程實現你會嗎?(不會呀~~~ md ...這都是些shenmegui)算法
其實,說多了都是淚,這些都是程序員的基本素質呀。。。 面tencent的時候,被一個總監,罵的阿彌陀佛麼麼噠. 今天在這裏和你們分享一下,個人血淚史。npm
工欲善其事,必先利其器segmentfault
一個程序員境界的提高,並不在於你寫的一首好代碼,更在於你能說出代碼背後的故事。ok~ 雞湯灌完了。咱們開始說方法了。
首先這幅圖你們必須記得很是清楚才行。
對了還有,
OSI七層模型你們應該爛熟於心的。
其中TCP處理transport層,主要是用來創建可靠的鏈接。 而創建鏈接的基礎,是他豐富的報文內容(md~超級多).咱們先來解釋一下。 首先,咱們TCP3次握手用的報文就是綠色的"TCP Flags"內容。 經過發送ACK,SYN包實現。具體涉及的Tag詳見:安全
Source Port / Destination Port:這個就是客戶端口(源端口)和服務器端口(目的端口). 端口就是用來區別主機中的不一樣進程,經過結合源IP和目的IP結合,得出惟一的TCP鏈接。服務器
Sequence Number(seqNumber): 通常由 客戶端發送,用來表示報文段中第一個數據字節在數據流中的序號,主要用來解決網絡包亂序的問題。
Acknowledgment Number(ACK): 即就是用來存放客戶端發來的seqNumber的下一個信號(seqNumber+1). 只有當 TCP flags中的ACK爲1時纔有效. 主要是用來解決不丟包的問題。
TCP flags: TCP中有6個首部,用來控制TCP鏈接的狀態.取值爲0,1.這6個有:URG,ACK,PSH,RST,SYN,FIN.
URG 當爲1時,用來保證TCP鏈接不被中斷, 而且將該次TCP內容數據的緊急程度提高(就是告訴電腦,你丫趕快把這個給resolve了)
ACK 一般是服務器端返回的。 用來表示應答是否有效。 1爲有效,0爲無效
PSH 表示,當數據包獲得後,立馬給應用程序使用(PUSH到最頂端)
RST 用來確保TCP鏈接的安全。 該flag用來表示 一個鏈接復位的請求。 若是發生錯誤鏈接,則reset一次,從新連。固然也能夠用來拒絕非法數據包。
SYN 同步的意思,一般是由客戶端發送,用來創建鏈接的。第一次握手時: SYN:1 , ACK:0. 第二次握手時: SYN:1 ACK:1
FIN 用來表示是否結束該次TCP鏈接。 一般當你的數據發送完後,會自動帶上FIN 而後斷開鏈接
恩,基本的TCP內容,你們應該掌握了吧。OK, go on.
仍是同樣, 先上張圖,讓你們先看一下。 上面你們已經基本瞭解了TCP裏面相應的字段,如今看看圖裏面的是否是以爲有些親切嘞?
其實,你們看上面的圖,差很少都已經可以摸清楚,每次發送請求的內容。其實,TCP3次握手是爲了創建 穩定可靠的鏈接。因此也就不存在神馬 2次鏈接等的怪癖。
(圖中flag說明:SYN包表示標誌位syn=1,ACK包表示標誌位ack=1,SYN+ACK包表示標誌位syn=1,ack=1)
如今,咱們來正式進入3次握手環節。
第一次握手. 客戶端向服務器發送一個SYN包,而且添加上seqNumber(假設爲x),而後進入SYN_SEND狀態,而且等待服務器的確認。
第二次握手: 服務器接受SYN包,而且進行確認,若是該請求有效,則將TCP flags中的ACK 標誌位置1, 而後將AckNumber置爲(seqNumber+1),而且再添加上本身的seqNumber(y), 完成後,返回給客戶端.服務器進入SYN_RECV狀態.(這裏服務端是發送SYN+ACK包)
第三次握手 客戶端接受ACK+SYN報文後,獲取到服務器發送seqNumber(y), 而且 將新頭部的AckNumber變爲(y+1).而後發送給服務器,完成TCP3次鏈接。此時服務器和客戶端都進入ESTABLISHED狀態.
回答一下這個比較尷尬的問題,爲何只有3次握手,而不是4次,或者2次?
很簡單呀,由於3次就夠了,幹嗎用4次。23333. 舉個例子吧,假如是2次的話, 可能會出現這樣一個狀況。
當客戶端發送一次請求A後,可是A在網絡延遲了好久, 接着客戶端又發送了一次B,可是此時A已經無效了。 接着服務器相應了B,並返回TCP鏈接頭,創建鏈接(這裏就2次哈)。 而後,A 歷經千山萬水終於到服務器了, 服務器一看有請求來了,則接受,因爲一開始A帶着的TCP格式都是正確的,那麼服務器,理所應當的也返回成功鏈接的flag,可是,此時客戶端已經判斷該次請求無效,廢棄了。 而後服務器,就這麼一直掛着(浪費資源),形成的一個問題是,md, 這個鍋是誰的? 因此,爲了保險起見,再補充一次鏈接就能夠了。因此3次是最合適的。在Chinese中,以3爲起稱爲多,若是你用4,5,6,7,8...次的話,這不更浪費嗎?
TCP4次揮手,是比較簡單的。你們對照上面那個圖,咱們一步一步進行一下講解。
第一次揮手: A機感受此時若是keep-alive比較浪費資源,則他提出了分手的請求。設置SeqNumber和AckNumber以後,向B機發送FIN包, 表示我這已經沒有數據給你了。而後A機進入FIN_WAIT_1狀態
第二次揮手:B機收到了A機的FIN包,已經知道了A機沒有數據再發送了。此時B機會給A機發送一個ACK包,而且將AckNumber 變爲 A機傳輸來的SeqNumber+1. 當A機接受到以後,則變爲FIN_WAIT_2狀態。表示已經獲得B機的許可,能夠進行關閉操做。不過此時,B機仍是能夠向A機發送請求的。
第三次揮手 B機向A機發送FIN包,請求關閉,至關於告訴A機,我這裏也沒有你要的數據了。而後B機進入CLOSE_WAIT狀態.(這裏還須要帶上SeqNumber,你們看圖說話就能夠了)
第四次揮手 A機接收到B機的FIN包以後,而後一樣,發送一個ACK包給B機。 B機接受到以後,就斷開了。 而A機 會等待2MSL以後,若是沒有回覆,確保服務器端確實是關閉了。而後A機也能夠關閉鏈接。A,B都進入了CLOSE狀態.
明白了嗎?
大哥~ 等等,什麼是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
NodeJS
ok~ 簡單吧。
親,那進程和線程區別是什麼嘞?
go die /(ㄒoㄒ)/~~
這算是計算機的基本知識吧。 首先咱們須要記住的是,進程包括線程。這很是重要。
進程就是系統分配資源的基本單位(好比CPU,內存等)
線程就是程序執行的最小單位
進程有本身的空間,若是一個進程崩潰不會引發其它進程的崩潰。
線程,沒有本身獨立的空間,多個線程共享的是進程的地址空間,固然處理一些基本的如程序計數器,一組寄存器和棧等。
若是一個線程崩潰,它所在的進程就崩潰了。 雖說,多進程很穩定,可是進程切換時,耗費的資源也是很大的。 因此對於大併發的nodeJS來講,使用多線程的效果要遠遠比多進程快,穩定。
1.系統在啓動一個進程的時候,會首先在資源中獨立一塊出來,在後臺創建一些列表進行維護。 而,線程是比進程低一個level的,因此建立線程所耗費的資源要遠遠比,建立進程的資源少。
因爲進程自己就比較複雜,因此若是進行進程切換的話,形成的性能損耗也是不言而喻的(由於多個進程獨立,在切換的時候還須要保證各自的獨立性)。 而線程切換就不一樣了,由於在處在同一進程下面,對於其餘的進程都是透明化的(內存共享),因此在進行進程切換時,所耗費的資源遠遠比進程切換的小。
在Linux和window下,CPU的分配是根據線程數來的,若是
總線程數<= CPU數量:並行運行 總線程數> CPU數量:併發運行
並行指的是,當你的CPU核數比線程數多的話,則會將每一個線程都分在一個CPU核裏進行處理。
併發指的是,當你的CPU核數比線程數少的話,則會利用「時間片輪轉進程調度算法」,對每一個線程進行同等的運行。
4.細化進程的處理,一般一個進程能夠拆分爲多個線程進行處理,就和模塊化處理是相似的,使用模塊化書寫的效果要遠遠比使用單main入口方式書寫 清晰,穩定。
親, 併發和並行有什麼共同點嗎?
恩~ 有的, 他們都有個‘並’子,字面上看起來都是同時執行的意思。
沒錯,固然只是字面上而已。
實際上,併發和並行是徹底不一樣的概念。 這裏主要和CPU核數有關。這裏爲了理解,拿線程來做爲參考吧。
當你的
總線程數<= CPU數量:並行運行 總線程數> CPU數量:併發運行
很明顯,並行實際上是真正意義上的同時執行。 當線程數< CPU核數時,每一個線程會獨立分配到一個CPU裏進行處理。
你們看過火影忍者嗎?
沒錯,就是鳴人 出關 口遁九尾以後。 他使用影分身,跑去各地支援同伴,對抗斑。 這裏類比來講,就能夠理解爲, 每一個CPU 都是鳴人的一個影分身,他們執行這各自不一樣的工做,可是,在同一時間上,他們都在運行。 這就是並行。
那併發嘞?
其實,併發有點難以理解,他作的工做其實,就是利用一系列算法實現,並行作的事。一個比較容易理解的就是「時間片輪轉進程調度算法」。
即: 在系統控制下,每一個線程輪流使用CPU,並且,每一個線程使用時間必須很短(好比10ms), 因此這樣切換下來。咱們(愚蠢的人類,哈哈哈), 天真的覺得任務,真的是在"並行"執行.
一開始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)進行通訊.
server.listen(0):在master和worker通訊過程,集羣中的worker會打開一個隨機端口共用,經過socket通訊像上例中的57803
固然你也能夠手動打開一個端口共享監聽。像這樣.
http.createServer(function(req, res) { res.writeHead(200); res.end("hello world\n"); }).listen(3000);
cluster對象的屬性和函數
cluster.setttings:配置集羣參數對象
cluster.isMaster:判斷是否是master節點*
cluster.isWorker:判斷是否是worker節點*
Event: 'fork': 監聽建立worker進程事件
Event: 'online': 監聽worker建立成功事件
Event: 'listening': 監聽worker開啓的http.listen
Event: 'disconnect': 監聽worker斷線事件
Event: 'exit': 監聽worker退出事件
Event: 'setup': 監聽setupMaster事件
cluster.setupMaster([settings]): 設置集羣參數
cluster.fork([env]): 建立worker進程
cluster.disconnect([callback]): 關閉worket進程*
cluster.worker: 得到當前的worker對象*
cluster.workers: 得到集羣中全部存活的worker對象*
經過cluster.worker得到的worker對象和相應的參數
worker.id: 進程ID號
worker.process: ChildProcess對象*
worker.suicide: 在disconnect()後,判斷worker是否自殺*
worker.send(message, [sendHandle]):* master給worker發送消息。注:worker給發master發送消息要用process.send(message)
worker.kill([signal='SIGTERM']): 殺死指定的worker,別名destory()*
worker.disconnect(): 斷開worker鏈接,讓worker自殺
Event: 'message': 監聽master和worker的message事件
Event: 'online': 監聽指定的worker建立成功事件
Event: 'listening': 監聽master向worker狀態事件
Event: 'disconnect': 監聽worker斷線事件
Event: 'exit': 監聽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
參照註釋代碼和上述的結果,咱們能夠很容易的獲得一個觸發邏輯。
運行過程是:
首先fork子進程
觸發fork事件
建立成功,觸發online事件
而後從新執行一遍app.js,經過isWorker判斷子進程
建立子進程服務->觸發master上的listening
st=>start: 首先fork子進程 op1=>operation: 觸發fork事件 op2=>operation: 建立成功,觸發online事件 op3=>operation: 而後從新執行一遍app.js,經過isWorker判斷子進程 op4=>operation: 建立子進程服務->觸發master上的listening e=>end st->op1->op2->op3->op4->e
上面只是建立滿負載子進程的流程。 但怎樣實現進程間的交互呢? 很簡單,master和worker監聽message事件,經過傳遞參數,進行交互。
cluster.worker.send(message[,handleFn]) master向worker發送信息
process.send(message[,handleFn]); worker向master發送信息
這個是多進程之間的通訊
communication
咱們來分解一下代碼塊:
//開啓master監聽worker的通訊 cluster.workers[id].on('message', function(msg){ //... }); //開啓worker監聽master的通訊 process.on('message', function(msg) { //... });
運行上面的demo. 這裏就不細說,整個流程,只看一下信息通訊這一塊了。
建立子進程,觸發listening事件
使用process.on監聽message
接受master發送過來的消息
再向master返回消息
st=>start: 建立子進程,觸發listening事件 op1=>operation: 使用process.on監聽message op2=>operation: 接受master發送過來的消息 op3=>operation: 再向master返回消息 op4=>operation: others e=>others st->op1->op2->op3->op4
如今,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裏還有其餘的參數.
-c NUM 設置併發的數量.eg: -c 100; //設置100次併發
-r NUM 設置發送幾輪的請求,即,總的請求數爲: -cNum*-rNum
可是, -r不能和-t一塊兒使用(爲何呢?你猜).eg: -r 20
-t NUM 測試持續時間,指你運行一次測試須要的時間,在timeout後,結束測試.
-f file. 用來測試file裏面的url路徑.file的尾綴須要爲.url. eg: -f girls.url
.
-b . 就是詢問開不開啓基準測試(benchmark)。 這個參數不過重要,有興趣的同窗,能夠下去學習一下。
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
能夠看出,基本上每一個負載上分配的請求的數目都差很少。這就已經達到了負載均衡的效果。
下一篇會對nodeJS已經相關的測試工具作一些介紹哦。
盡請期待。
ending~
你們若是感興趣,給我一杯咖啡喝喝吧~
轉載請註明出處和做者
原文鏈接:http://www.javashuo.com/article/p-xiidelvz-g.html