系列目錄
- NodeJS與Django協(xié)同應(yīng)用開(kāi)發(fā)(0) node.js基礎(chǔ)知識(shí)
- NodeJS與Django協(xié)同應(yīng)用開(kāi)發(fā)(1)原型搭建
- NodeJS與Django協(xié)同應(yīng)用開(kāi)發(fā)(2)業(yè)務(wù)框架
- NodeJS與Django協(xié)同應(yīng)用開(kāi)發(fā)(3)測(cè)試與優(yōu)化
- NodeJS與Django協(xié)同應(yīng)用開(kāi)發(fā)(4)部署
不管怎樣嗦锐,當(dāng)你想要分享一個(gè)東西的時(shí)候你就逃不開(kāi)要解釋它是什么北滥,它是做什么用的。所以作為基礎(chǔ)知識(shí)儲(chǔ)備,這里就介紹一下node.js以及其最重要的模塊之一Socket.io髓废。
node.js
對(duì)于node.js最多的介紹就是:它是基于異步事件驅(qū)動(dòng)的高性能非阻塞IO框架捐康。
所謂非阻塞IO模型(non-blocking I/O model)白群,講到這個(gè)恐怕得從最初的服務(wù)器網(wǎng)絡(luò)模型說(shuō)起屈芜。
最初的服務(wù)器都是單進(jìn)程的阻塞IO,所有的請(qǐng)求都在同一個(gè)進(jìn)程里連續(xù)處理喻粹,如果當(dāng)前請(qǐng)求耗費(fèi)了較長(zhǎng)的CPU時(shí)間蟆融,那么其他后續(xù)所有請(qǐng)求都要等待這個(gè)請(qǐng)求處理結(jié)束后才能處理,而在處理期間這個(gè)進(jìn)程也會(huì)被掛起守呜。所謂是一卡全都卡型酥。
在這之后發(fā)展出的是多進(jìn)程多線(xiàn)程的網(wǎng)絡(luò)模型。首先是簡(jiǎn)單的多進(jìn)程模型:對(duì)于每一個(gè)連接都分配一個(gè)進(jìn)程查乒。這種做法簡(jiǎn)單粗暴得解決了一個(gè)請(qǐng)求阻塞導(dǎo)致全部請(qǐng)求都阻塞的問(wèn)題冕末,在請(qǐng)求量稍高但是不那么高的場(chǎng)景下完全足夠了。但是當(dāng)請(qǐng)求量一旦多起來(lái)侣颂,就又暴露出2個(gè)缺點(diǎn):
- 一個(gè)進(jìn)程所需的系統(tǒng)資源是不容忽視的,但是相比處理單個(gè)請(qǐng)求來(lái)說(shuō)又顯得殺雞用牛刀枪孩,高并發(fā)情況下系統(tǒng)資源極易耗盡憔晒。
- 同時(shí)能夠處理的進(jìn)程數(shù)取決于CPU核數(shù),進(jìn)程越多就意味著更頻繁的進(jìn)程調(diào)度蔑舞,也就意味著更多的時(shí)間被花在了切換進(jìn)程上拒担,導(dǎo)致整個(gè)系統(tǒng)的性能下降。
于是又加入了多線(xiàn)程模型攻询,讓一個(gè)進(jìn)程可以處理多個(gè)請(qǐng)求从撼,以求降低系統(tǒng)資源的消耗。雖然多線(xiàn)程對(duì)于上述2個(gè)問(wèn)題都能有所改善钧栖,但是治標(biāo)不治本低零。創(chuàng)建線(xiàn)程開(kāi)銷(xiāo)小于創(chuàng)建進(jìn)程,切換線(xiàn)程開(kāi)銷(xiāo)也小于切換進(jìn)程(原因在于切換進(jìn)程涉及到換頁(yè)拯杠,涉及到代碼段換入換出掏婶,線(xiàn)程不涉及代碼段,只涉及數(shù)據(jù)段潭陪,也就是寄存器狀態(tài)和棧狀態(tài))雄妥,不過(guò)這只是把壓力閾值提高了一些而已最蕾,依然有一部分系統(tǒng)資源、CPU時(shí)間浪費(fèi)在了非處請(qǐng)請(qǐng)求上老厌。
到這里為止所說(shuō)的都還是阻塞IO模型瘟则,也就是一個(gè)處理請(qǐng)求的單元從收到請(qǐng)求,處理請(qǐng)求枝秤,到返回結(jié)果之間都是線(xiàn)性的醋拧,任何一步因?yàn)槿魏卧蜃枞硕紩?huì)導(dǎo)致這個(gè)處理單元的阻塞,并被掛起宿百。假如我們?cè)谝粋€(gè)請(qǐng)求的處理過(guò)程中需要查詢(xún)數(shù)據(jù)庫(kù)趁仙,或是依賴(lài)外部服務(wù),而這些操作又有很大的延遲垦页,那么在這段時(shí)間內(nèi)處理單元就會(huì)被掛起雀费,但由于這個(gè)請(qǐng)求沒(méi)有處理完,資源就不能拿出來(lái)處理其他的請(qǐng)求痊焊。所以哪怕服務(wù)負(fù)載特別高盏袄,資源的利用率也不高。所以就催生出了非阻塞IO模型薄啥。
在非阻塞IO模型里辕羽,最重要的一點(diǎn)就是任何操作都不會(huì)將當(dāng)前處理單元掛起,從而能夠允許其執(zhí)行之后的請(qǐng)求垄惧。這么做的好處在于更大化得利用了CPU時(shí)間刁愿,讓CPU時(shí)刻都在處理需要處理的事務(wù)。所以在這種模型下到逊,多進(jìn)程多線(xiàn)程就顯得沒(méi)那么必要铣口,只要能夠完全利用CPU的核數(shù),單線(xiàn)程也完全足夠觉壶,且性能還要優(yōu)于之前的模型脑题。這也是為什么現(xiàn)在的負(fù)載均衡器都建議按照CPU核數(shù)來(lái)部署應(yīng)用。
舉個(gè)例子铜靶,假設(shè)我們只有一個(gè)進(jìn)程(單線(xiàn)程)叔遂,此時(shí)來(lái)了3個(gè)請(qǐng)求,第一個(gè)請(qǐng)求需要查詢(xún)數(shù)據(jù)庫(kù)争剿,耗時(shí)500ms已艰,第二個(gè)請(qǐng)求需要調(diào)用外部服務(wù),耗時(shí)2000ms蚕苇,第三個(gè)請(qǐng)求查詢(xún)系統(tǒng)時(shí)間旗芬,耗時(shí)5ms。
在普通的阻塞IO模型里捆蜀,第一個(gè)請(qǐng)求來(lái)了之后先花500ms處理完疮丛,再花2000ms處理第二個(gè)請(qǐng)求幔嫂,再花5ms處理第三個(gè)請(qǐng)求。也就是說(shuō)第一個(gè)用戶(hù)500ms后得到了結(jié)果誊薄,第二個(gè)用戶(hù)卻用了2500ms得到結(jié)果履恩,第三個(gè)用戶(hù)僅僅只要查一下時(shí)間卻用了2505ms才得到結(jié)果。
而在非阻塞IO模型里呢蔫,同樣第一個(gè)請(qǐng)求過(guò)來(lái)后切心,查詢(xún)工作交給數(shù)據(jù)庫(kù),緊接著就開(kāi)始處理第二個(gè)請(qǐng)求片吊。同樣任務(wù)交給外部服務(wù)后绽昏,就開(kāi)始處理第三個(gè)請(qǐng)求。第三個(gè)請(qǐng)求很快就得到了結(jié)果俏脊,并返回給了用戶(hù)全谤,此時(shí)才經(jīng)過(guò)了5ms。在500ms的時(shí)間點(diǎn)爷贫,數(shù)據(jù)庫(kù)查詢(xún)工作結(jié)束认然,并返回給了第一個(gè)用戶(hù),在2000ms的時(shí)間點(diǎn)漫萄,外部服務(wù)返回了結(jié)果卷员,于是就返回給了第二個(gè)用戶(hù)。
下面這個(gè)表格就很清楚的展示了兩種模型的差異腾务。
發(fā)起請(qǐng)求到獲得結(jié)果時(shí)間表 | 阻塞IO模型 | 非阻塞IO模型 |
---|---|---|
用戶(hù)1 | 500ms | 500ms |
用戶(hù)2 | 2500ms | 2000ms |
用戶(hù)3 | 2505ms | 5ms |
可以看出毕骡,非阻塞IO模型在請(qǐng)求量很多的時(shí)候,對(duì)于每一個(gè)用戶(hù)而言都有很好的體驗(yàn)岩瘦。
這一系列的演化挺峡,舉個(gè)例子,就好比有一個(gè)水庫(kù)需要排水担钮,最初你只有一個(gè)水管(單進(jìn)程)排水,突然有些水草把管子堵住了(當(dāng)前請(qǐng)求阻塞)尤仍,那在你把水草清理掉之前水都流不出來(lái)(所有請(qǐng)求阻塞)箫津。后來(lái)你決定再多買(mǎi)一些水管(多進(jìn)程),但是受財(cái)力限制你只能買(mǎi)100根(系統(tǒng)資源耗盡)宰啦。不過(guò)水草堵了之后依然需要人力來(lái)清理苏遥,但是依然財(cái)力所限你只能請(qǐng)4個(gè)工人(CPU核數(shù)),而工人大部分時(shí)間又花在了在水管間走來(lái)走去赡模,而不是實(shí)際清理水草上(進(jìn)程切換)田炭。這時(shí)候有個(gè)公司過(guò)來(lái)找你說(shuō)他們有種技術(shù)可以把25根管子包進(jìn)一個(gè)大管子里,看上去你只用了4個(gè)水管排水(多線(xiàn)程)漓柑,而且還比原來(lái)便宜(減少系統(tǒng)資源開(kāi)銷(xiāo))教硫。但是水管依然會(huì)堵叨吮,工人依然要找究竟哪個(gè)小管子堵了(線(xiàn)程依然需要切換)。再后來(lái)又有個(gè)公司過(guò)來(lái)找你說(shuō)有一套新型管子瞬矩,能夠在水草堵住的時(shí)候自動(dòng)切換到備用水管茶鉴,并自動(dòng)開(kāi)始清理水草,永遠(yuǎn)能夠讓水順利流出(非阻塞模型)景用,價(jià)值是原來(lái)的一個(gè)工人和25根水管的總和涵叮。于是你非常開(kāi)心的辭退了所有工人,賣(mài)了原先的所有水管伞插,買(mǎi)了4套新型管子(最大化利用CPU核數(shù))割粮。此后就再也不用擔(dān)心水排不出了。
這就是非阻塞IO模型媚污,而node.js的設(shè)計(jì)初衷就是搭建靈活的網(wǎng)絡(luò)應(yīng)用舀瓢,于是在此之上選用了JavaScript這一靈活的語(yǔ)言和異步事件驅(qū)動(dòng)設(shè)計(jì)。
所謂事件驅(qū)動(dòng)(event-driven)杠步,通俗來(lái)講就是發(fā)生了什么事氢伟,我們才做相應(yīng)的處理。這里的事件可以是一個(gè)用戶(hù)操作幽歼,可以是一次內(nèi)容變化朵锣,也可以是一次網(wǎng)絡(luò)請(qǐng)求,甚至可以是一次系統(tǒng)異常甸私。凡是我們關(guān)心的诚些,都可以成為事件,然后我們?cè)僮鱿鄳?yīng)的處理皇型。
而異步(asynchronous communication)的意思就是說(shuō)诬烹,當(dāng)一個(gè)調(diào)用到來(lái)時(shí),并不等到有結(jié)果了再返回弃鸦,而是直接返回绞吁,有結(jié)果了再通知調(diào)用方。
同步與異步唬格、阻塞與非阻塞家破,看上去非常相似但本質(zhì)是不同的。同步異步關(guān)心的是消息通信機(jī)制购岗,阻塞非阻塞關(guān)心的是等待調(diào)用結(jié)果時(shí)的狀態(tài)汰聋。
以下內(nèi)容引用自知乎用戶(hù) 盧毅 在問(wèn)題怎樣理解阻塞非阻塞與同步異步的區(qū)別?中的回答:
[同步異步]
舉個(gè)通俗的例子:
你打電話(huà)問(wèn)書(shū)店老板有沒(méi)有《分布式系統(tǒng)》這本書(shū)喊积,如果是同步通信機(jī)制烹困,書(shū)店老板會(huì)說(shuō),你稍等乾吻,”我查一下"髓梅,然后開(kāi)始查啊查拟蜻,等查好了(可能是5秒,也可能是一天)告訴你結(jié)果(返回結(jié)果)女淑。
而異步通信機(jī)制瞭郑,書(shū)店老板直接告訴你我查一下啊,查好了打電話(huà)給你鸭你,然后直接掛電話(huà)了(不返回結(jié)果)屈张。然后查好了,他會(huì)主動(dòng)打電話(huà)給你袱巨。在這里老板通過(guò)“回電”這種方式來(lái)回調(diào)阁谆。
[阻塞非阻塞]
還是上面的例子,
你打電話(huà)問(wèn)書(shū)店老板有沒(méi)有《分布式系統(tǒng)》這本書(shū)愉老,你如果是阻塞式調(diào)用场绿,你會(huì)一直把自己“掛起”,直到得到這本書(shū)有沒(méi)有的結(jié)果嫉入,如果是非阻塞式調(diào)用焰盗,你不管老板有沒(méi)有告訴你,你自己先一邊去玩了咒林, 當(dāng)然你也要偶爾過(guò)幾分鐘check一下老板有沒(méi)有返回結(jié)果熬拒。
在這里阻塞與非阻塞與是否同步異步無(wú)關(guān)。跟老板通過(guò)什么方式回答你結(jié)果無(wú)關(guān)垫竞。
而node.js在運(yùn)行時(shí)是采用單線(xiàn)程架構(gòu)的澎粟,又綜合了異步與非阻塞的優(yōu)點(diǎn),這就是為什么它還能保持高性能的原因欢瞪。至于為什么node.js選用了JavaScript和異步事件驅(qū)動(dòng)活烙,那就不是這里論述的話(huà)題了,只能說(shuō)它就是這么選了遣鼓,它樂(lè)意啸盏。
題外話(huà):
node.js選擇了JavaScript,所以就選擇了Google V8引擎骑祟,而原本V8是為了瀏覽器所設(shè)計(jì)的高性能js解釋器回懦,因此并不具備多線(xiàn)程能力,所以node.js也只能是單線(xiàn)程架構(gòu)曾我。但我認(rèn)為如果技術(shù)上能夠讓node.js支持多線(xiàn)程,性能并不會(huì)高于單線(xiàn)程健民。引入多線(xiàn)程就勢(shì)必引入鎖抒巢,在某些情況下加鎖的開(kāi)銷(xiāo)是不能忽略的,所以不見(jiàn)得多線(xiàn)程就是好秉犹。不過(guò)單線(xiàn)程的node.js并不能100%發(fā)揮出多核CPU的能力蛉谜,所以通過(guò)child_process.fork()稚晚,也就是現(xiàn)在的cluster模塊,允許node在多進(jìn)程下編程型诚。這樣更加能夠利用硬件資源來(lái)支撐高并發(fā)高訪問(wèn)量客燕。
Socket.io
相比node.js, socket.io就簡(jiǎn)單得多了狰贯。這是一個(gè)用來(lái)構(gòu)建實(shí)時(shí)web應(yīng)用的JavaScript庫(kù)也搓,對(duì)于瀏覽器的客戶(hù)端和后臺(tái)服務(wù)器端有兩套接口相似的庫(kù),與node.js相同涵紊,它也是事件驅(qū)動(dòng)的傍妒。
多數(shù)情況下socket.io是基于websocket協(xié)議的,但是對(duì)于不支持的瀏覽器它也能夠自動(dòng)回退到flash或是長(zhǎng)輪詢(xún)摸柄。
socket.io的好處就在于自動(dòng)選擇合適的協(xié)議颤练,簡(jiǎn)單易上手的api∏海可以說(shuō)node.js的熱度有相當(dāng)一部分是由socket.io撐起來(lái)的嗦玖。
對(duì)于socket.io也沒(méi)有那么多可以介紹的,各種特性實(shí)例代碼一看便知跃脊,所以這篇文章就先寫(xiě)到這吧宇挫,后文會(huì)搭建出一套node.js+socket.io+Django+redis的原型,更詳細(xì)的內(nèi)容可以移步那里匾乓。