摘要:一般由客戶端發送,用來表示報文段中第一個數據字節在數據流中的序號,主要用來解決網絡包亂序的問題。為有效,為無效表示,當數據包得到后,立馬給應用程序使用到最頂端用來確保連接的安全。親,那進程和線程區別是什么嘞這算是計算機的基本知識吧。
在正文之前,我想問大家一個問題:
問:親,你有基礎嗎?
答: 有啊,你說前端嗎? 不就是HTML,JS,CSS 嗎? so easy~
問: oh-my-zsh... 好吧,那問題來了,挖掘機技術哪家強... 開玩笑。
現在才是問題的正內容。
你知道TCP的基本內容嗎?(母雞啊~)
好吧,那你知道TCP的3次握手,4次揮手嗎?(知道一點點)
恩,好,那什么是進程呢?什么是線程呢?(母雞啊。。)
那并發和并行又是什么呢?(母雞啊)
OMG, 那nodeJS多進程實現你會嗎?(不會呀~~~ md ...這都是些shenmegui)
其實,說多了都是淚,這些都是程序員的基本素質呀。。。 面tencent的時候,被一個總監,罵的阿彌陀佛么么噠. 今天在這里和大家分享一下,我的血淚史。
TCP內容工欲善其事,必先利其器
一個程序員境界的提升,并不在于你寫的一首好代碼,更在于你能說出代碼背后的故事。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.
What"s TCP 3次握手還是一樣, 先上張圖,讓大家先看一下。 上面大家已經基本了解了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次揮手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 "); }).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 "); }).listen(3000);cluster對應API
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->op4nodeJS負載均衡
現在,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~
大家如果感興趣,給我一杯咖啡喝喝吧~
轉載請注明出處和作者
原文連接:https://segmentfault.com/a/1190000004569460
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/8733.html
摘要:前言月份開始出沒社區,現在差不多月了,按照工作的說法,就是差不多過了三個月的試用期,準備轉正了一般來說,差不多到了轉正的時候,會進行總結或者分享會議那么今天我就把看過的一些學習資源主要是博客,博文推薦分享給大家。 1.前言 6月份開始出沒社區,現在差不多9月了,按照工作的說法,就是差不多過了三個月的試用期,準備轉正了!一般來說,差不多到了轉正的時候,會進行總結或者分享會議!那么今天我就...
摘要:特意對前端學習資源做一個匯總,方便自己學習查閱參考,和好友們共同進步。 特意對前端學習資源做一個匯總,方便自己學習查閱參考,和好友們共同進步。 本以為自己收藏的站點多,可以很快搞定,沒想到一入匯總深似海。還有很多不足&遺漏的地方,歡迎補充。有錯誤的地方,還請斧正... 托管: welcome to git,歡迎交流,感謝star 有好友反應和斧正,會及時更新,平時業務工作時也會不定期更...
閱讀 2044·2021-10-08 10:05
閱讀 1886·2021-09-22 15:31
閱讀 3007·2021-09-22 15:13
閱讀 3485·2021-09-09 09:34
閱讀 2080·2021-09-03 10:46
閱讀 3119·2019-08-30 15:56
閱讀 1702·2019-08-30 15:53
閱讀 2355·2019-08-30 15:44