Node.js的異步I/O

1. 多任務(wù)的實(shí)現(xiàn)

多任務(wù)的實(shí)現(xiàn)只有三種方式:

  1. 多進(jìn)程
  2. 單進(jìn)程+多線程
  3. 多進(jìn)程+多線程

第三種過(guò)于復(fù)雜脉幢,實(shí)現(xiàn)很少歪沃。多進(jìn)程和多線程都會(huì)消耗 cpu,在線程和進(jìn)程之間切換也會(huì)消耗 cpu嫌松,但是進(jìn)程的開(kāi)銷(xiāo)更大沪曙,所以,多任務(wù)的實(shí)現(xiàn)一般都是單進(jìn)程下開(kāi)啟多個(gè)線程萎羔。

但是液走,起初大部分實(shí)現(xiàn) I/O 操作的庫(kù)都是阻塞型 I/O。因此贾陷,多線程下缘眶,某個(gè)線程進(jìn)行 I/O 操作,當(dāng)前線程就會(huì)被阻塞髓废。雖然這種方式不會(huì)影響其他線程巷懈,最直觀的就是多個(gè)用戶連接之間不會(huì)因?yàn)橐粋€(gè)連接進(jìn)行 I/O 操作就阻塞了其他用戶。但是這依然有一個(gè)問(wèn)題慌洪,阻塞期間 cpu 等待 I/O 操作的結(jié)束顶燕,浪費(fèi)了很多性能,導(dǎo)致這種方式實(shí)現(xiàn)的并發(fā)性能和瓶頸都不高冈爹。

2. 異步和非阻塞

阻塞和非阻塞是對(duì)于操作系統(tǒng)而言的涌攻,是操作系統(tǒng)執(zhí)行 I/O 操作的兩種方式。

內(nèi)核實(shí)現(xiàn)非阻塞 I/O 的幾種方法:

  1. read 輪詢模式
    cpu 一直在重復(fù)執(zhí)行 read 操作频伤,直到 I/O 操作完成恳谎;

  2. select 輪詢
    輪詢檢查文件描述符中的狀態(tài),狀態(tài)未讀取完成了再調(diào)用 read 方法憋肖;

  3. poll
    解決了select只能檢查1024個(gè)文件的限制因痛;

  4. epoll
    收到 I/O 事件的通知之后才會(huì)執(zhí)行相關(guān)操作,否則處于休眠狀態(tài)瞬哼;

異步和同步是結(jié)果婚肆,基于內(nèi)核實(shí)現(xiàn)非阻塞 I/O 的原理,單線程模式下的非阻塞 I/O 的結(jié)果仍然是同步坐慰!

非阻塞 I/O 方式较性,雖然一定程度上減少了對(duì) cpu 的損耗用僧,但是其本質(zhì)還是需要等待 I/O 操作的完成,只是等待期間赞咙,cpu 可能處于休眠责循,可能處于輪詢,這個(gè)操作性能上可能比阻塞 I/O 更好攀操,但是結(jié)果仍然是同步院仿;

需要特別注意的是:

非阻塞 I/O 和阻塞 I/O 區(qū)別并不大!K俸汀歹垫!

所以,真正意義上的異步是通過(guò)多線程 + 事件響應(yīng)實(shí)現(xiàn)的颠放。Node.js 中的 libuv 就是封裝了 Linux 和 Window 兩種操作系統(tǒng)下異步 API 的實(shí)現(xiàn)排惨。

再次提醒,異步 I/O 和非阻塞 I/O 是不同的概念碰凶。異步 I/O 是結(jié)果暮芭,非阻塞 I/O 是操作系統(tǒng)執(zhí)行 I/O 的一種形式,且非阻塞 I/O 意義并不大欲低。

3. I/O的概念

IO 在計(jì)算機(jī)中指 Input/Output辕宏,也就是輸入和輸出。由于程序和運(yùn)行時(shí)數(shù)據(jù)是在內(nèi)存中駐留砾莱,由CPU這個(gè)超快的計(jì)算核心來(lái)執(zhí)行瑞筐,涉及到數(shù)據(jù)交換的地方,通常是磁盤(pán)恤磷、網(wǎng)絡(luò)等面哼,就需要 I/O 接口。實(shí)現(xiàn)了 I/O 接口和功能的設(shè)備叫做 I/O 設(shè)備扫步,比如磁盤(pán)、網(wǎng)卡等等匈子。

代碼的運(yùn)行河胎、計(jì)算、線程之間的切換等都是 cpu 來(lái)執(zhí)行虎敦。cpu 執(zhí)行的速度遠(yuǎn)遠(yuǎn)高于 I/O 設(shè)備的處理速度游岳,內(nèi)存的存取數(shù)據(jù)的速度也遠(yuǎn)遠(yuǎn)超出 I/O 設(shè)備存取數(shù)據(jù)的速度。

在同步 I/O 的框架中其徙,一個(gè)線程中如果開(kāi)始了 I/O 操作胚迫,那么這個(gè)線程中其他的操作都需要等待 I/O 操作的結(jié)束才能繼續(xù)進(jìn)行,也就是超高速的 cpu 等待龜速的 I/O 操作唾那。因此访锻,同步 I/O 的框架中存在的問(wèn)題就是性能的瓶頸就是 I/O 操作的速度。

另外,I/O 的同步和異步并不是物理屬性期犬,也就是說(shuō)和設(shè)備無(wú)關(guān)河哑,不是說(shuō) I/O 設(shè)備就只支持同步 I/O 。I/O 的同步和異步與否取決于功能的實(shí)現(xiàn)龟虎,也就是 API 背后使用的是何種實(shí)現(xiàn)方式璃谨。

Ryan Dahl 就提出,大部分人不設(shè)計(jì)一種更簡(jiǎn)單有效的程序的原因是因?yàn)樗麄兪褂玫搅送?I/O 的庫(kù)鲤妥。因?yàn)橥?I/O 只需要等待即可佳吞,而異步 I/O 涉及到事件、輪詢棉安、消息容达、通知等一系列開(kāi)發(fā)任務(wù)。說(shuō)白了就是懶??~~所以垂券,Ryan Dahl 就基于 Javascript 花盐,使用異步 I/O 的方式實(shí)現(xiàn)了許多支持服務(wù)器開(kāi)發(fā)的庫(kù)和接口。

PS:雖然現(xiàn)在的程序員使用異步編程特別普遍菇爪,甚至如果你不使用異步編程算芯,很可能會(huì)因?yàn)闈撛诘男阅軉?wèn)題被人嘲諷。但是在若干年前凳宙,當(dāng)時(shí)流行的 PHP 就是完全的阻塞編程熙揍,壓根不提供異步和多線程的 Api。而當(dāng)時(shí)的很多高級(jí)語(yǔ)言雖然提供異步的支持氏涩,但是程序員普遍更喜歡使用同步編程届囚。在眾多的高級(jí)語(yǔ)言中,將異步作為主要編程方式和設(shè)計(jì)理念的是尖,Node是首個(gè)意系。所以,了解了當(dāng)時(shí)的環(huán)境就能更好的了解 Node.js 誕生的意義和目的了饺汹。

4. 解決的問(wèn)題

源起:基于(阻塞性I/O +多線程)原理的多任務(wù)服務(wù)器存在的問(wèn)題

當(dāng)遇到并發(fā)問(wèn)題時(shí)蛔添,比如多個(gè)客戶端向服務(wù)器發(fā)起了連接請(qǐng)求。其他語(yǔ)言比如 Java 等都是為一個(gè)新的客戶連接創(chuàng)建一個(gè)線程兜辞,一個(gè)線程的開(kāi)銷(xiāo)大概是2M迎瞧,所以一個(gè)內(nèi)存為 8GB 的服務(wù)器大概支持的同時(shí)連接數(shù)為4000。因此更大的并發(fā)勢(shì)必造成更大的硬件成本逸吵。不僅如此凶硅,多服務(wù)器存在時(shí),一個(gè)請(qǐng)求可能被不同的服務(wù)器處理扫皱,所以所有服務(wù)器之間必須共享資源足绅,由此會(huì)增加架構(gòu)的復(fù)雜性捷绑。

也就是說(shuō),問(wèn)題的核心是:

多線程原理的多任務(wù)系統(tǒng)隨著并發(fā)數(shù)的提升會(huì)帶來(lái)硬件成本和架構(gòu)復(fù)雜性的提升编检。

5. 如何解決問(wèn)題

  1. V8 javascript 引擎本身性能就比較高胎食;
    V8 javascript 引擎由 C++ 編寫(xiě),該引擎具備可移植性允懂,Node.js 就將它用在了服務(wù)器開(kāi)發(fā)上厕怜;

  2. 非阻塞I/O;
    javascript 本身是單線程了蕾总,V8 javascript 引擎也是如此粥航,所以 Node.js 為 javascript 提供了非阻塞性 I/O 的特性。另外生百,javascript 在瀏覽器中應(yīng)用時(shí)递雀,Web 為其整合提供了諸如 DOM、BOM 等很多瀏覽器 Api蚀浆。 Node.js 也一樣缀程,,除了 ECMAScript 之外市俊,Node.js 提供了很多服務(wù)器開(kāi)發(fā)相關(guān)的 Api杨凑;

  3. 事件響應(yīng)機(jī)制
    事件響應(yīng)說(shuō)白了就是 event loop,在 Node.js 剛問(wèn)世時(shí)摆昧,這種思想還很少撩满,但是如今卻是非常的常見(jiàn),比如 iOS 中的 runtime绅你、runloop伺帘。

綜上,Node.js 使用上述的三種手段來(lái)解決了面臨的問(wèn)題忌锯,但是最關(guān)鍵的仍然是事件響應(yīng)機(jī)制和線程池的管理伪嫁,下面將會(huì)介紹真正的異步 I/O 的實(shí)現(xiàn)原理。

6. 真正的異步 I/O

Node.js 中實(shí)現(xiàn)異步 I/O 的框架如下:


libuv架構(gòu)

Node.js 的本質(zhì)也是開(kāi)啟多線程汉规,至于執(zhí)行 I/O 操作非阻塞還是阻塞礼殊,其實(shí)并不重要,估計(jì)大部分仍然是阻塞针史。但是 java 也是多線程,那么這兩者有什么區(qū)別呢碟狞?為什么node.js 的并發(fā)數(shù)可以比 java 高那么多啄枕?

Java一個(gè)用戶一個(gè)線程,但是 Node.js 中只有需要進(jìn)行阻塞操作時(shí)族沃,比如 I/O 操作频祝、網(wǎng)絡(luò)請(qǐng)求操作時(shí)泌参,這個(gè)時(shí)候才會(huì)開(kāi)啟線程。也就是說(shuō)常空,因?yàn)橹挥性谛枰枞麜r(shí)才會(huì)開(kāi)啟線程沽一,所以相同的用戶連接數(shù),Node.js 中線程的開(kāi)啟時(shí)間會(huì)相對(duì)于 Java 中的少漓糙,那么最終就會(huì)造成最大并發(fā)數(shù)的區(qū)別铣缠。

其原理如下:


異步I/O

I/O 操作無(wú)論阻塞還是非阻塞,其實(shí)區(qū)別并不大昆禽。所以說(shuō)蝗蛙,node.js 和 java 最大的不同就是:

  • java 是為每個(gè)用戶分配一個(gè)進(jìn)程,進(jìn)程中執(zhí)行 I/O 操作時(shí)醉鳖,就會(huì)阻塞捡硅,此時(shí) cpu 就在等待?應(yīng)該是在等待盗棵,如果不等待壮韭,那么就必須有通知等機(jī)制來(lái)完成通知和響應(yīng)。
  • Node.js 是所有的用戶連接都在一個(gè)線程中纹因,當(dāng)需要 I/O 操作時(shí)喷屋,才從線程池中分配線程去進(jìn)行 I/O 操作,此時(shí)主線程繼續(xù)執(zhí)行后面的操作辐怕,I/O 線程會(huì)阻塞逼蒙。關(guān)鍵就在于,因?yàn)橛辛耸录氖琛⑼ㄖ润w系的支持是牢,此時(shí)的阻塞不需要 cpu 的等待,所以陕截,這里才是性能提高的關(guān)鍵驳棱。

另外,事件循環(huán)和通知機(jī)制就真的不再詳細(xì)解釋了农曲,附圖一張:

image.png

整個(gè)異步流程如下:


Node.js中的異步流程

注:本章主要參考《深入淺出Node.js》

7. Node.js的局限性

Node.js 之所以廣泛用于 Web 類(lèi)的應(yīng)用中社搅,是因?yàn)檫@類(lèi)應(yīng)用在,服務(wù)器的主要任務(wù)是 I/O 密集型而不是計(jì)算密集型乳规。

I/O 密集型而不是計(jì)算密集型就不詳細(xì)介紹了形葬,可以參考之前的文章,一言以蔽之:

  • I/O 密集型服務(wù)器的主要任務(wù)是資源的存和取暮的,單個(gè)任務(wù)對(duì) cpu 的消耗不大笙以;
  • 計(jì)算密集型服務(wù)器的主要任務(wù)是執(zhí)行代碼中的運(yùn)算,單個(gè)任務(wù)對(duì) cpu 消耗大冻辩;

所以猖腕,Node.js 的服務(wù)器如果想大并發(fā)拆祈,那就不能有非常復(fù)雜的處理。

常見(jiàn)場(chǎng)景:

  1. IM 系統(tǒng)
    在線人數(shù)很多倘感,但是在服務(wù)端的處理上放坏,基本只需要 get 和 insert 數(shù)據(jù);
  2. 電商系統(tǒng)
    同時(shí)購(gòu)物的人數(shù)很多老玛,但是服務(wù)器上

8. 總結(jié):

一般的服務(wù)器都是阻塞性 I/O +多線程來(lái)實(shí)現(xiàn)多任務(wù)體系以支持高并發(fā)淤年。而 Node.js 采用的是 Javascript ,其本身就是單線程逻炊,支持各種異步操作互亮,再配上 Node.js 自身實(shí)現(xiàn)的異步 I/O 的網(wǎng)絡(luò)處理的各種庫(kù),最終完美實(shí)現(xiàn)了多線程+事件機(jī)制的高性能高并發(fā)服務(wù)器余素。

再次提醒:Node.js 異步的實(shí)現(xiàn)的關(guān)鍵不在于多線程豹休,而在于將 I/O 操作進(jìn)行多線程處理以達(dá)到不阻塞執(zhí)行代碼的線程的目的。Java 雖然也是多線程桨吊,但是代碼執(zhí)行和 I/O 操作總是處在一個(gè)線程威根。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市视乐,隨后出現(xiàn)的幾起案子洛搀,更是在濱河造成了極大的恐慌,老刑警劉巖佑淀,帶你破解...
    沈念sama閱讀 219,188評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件留美,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡伸刃,警方通過(guò)查閱死者的電腦和手機(jī)谎砾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)捧颅,“玉大人景图,你說(shuō)我怎么就攤上這事〉镅疲” “怎么了挚币?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,562評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)扣典。 經(jīng)常有香客問(wèn)我妆毕,道長(zhǎng),這世上最難降的妖魔是什么贮尖? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,893評(píng)論 1 295
  • 正文 為了忘掉前任设塔,我火速辦了婚禮,結(jié)果婚禮上远舅,老公的妹妹穿的比我還像新娘闰蛔。我一直安慰自己,他們只是感情好图柏,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,917評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布序六。 她就那樣靜靜地躺著,像睡著了一般蚤吹。 火紅的嫁衣襯著肌膚如雪例诀。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,708評(píng)論 1 305
  • 那天裁着,我揣著相機(jī)與錄音繁涂,去河邊找鬼。 笑死二驰,一個(gè)胖子當(dāng)著我的面吹牛扔罪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播桶雀,決...
    沈念sama閱讀 40,430評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼矿酵,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了矗积?” 一聲冷哼從身側(cè)響起全肮,我...
    開(kāi)封第一講書(shū)人閱讀 39,342評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎棘捣,沒(méi)想到半個(gè)月后辜腺,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,801評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡乍恐,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,976評(píng)論 3 337
  • 正文 我和宋清朗相戀三年评疗,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片禁熏。...
    茶點(diǎn)故事閱讀 40,115評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡壤巷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出瞧毙,到底是詐尸還是另有隱情胧华,我是刑警寧澤,帶...
    沈念sama閱讀 35,804評(píng)論 5 346
  • 正文 年R本政府宣布宙彪,位于F島的核電站矩动,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏释漆。R本人自食惡果不足惜悲没,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,458評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望男图。 院中可真熱鬧示姿,春花似錦甜橱、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,008評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至子檀,卻和暖如春镊掖,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背褂痰。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,135評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工亩进, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留涂佃,地道東北人抬吟。 一個(gè)月前我還...
    沈念sama閱讀 48,365評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像吟策,于是被迫代替她去往敵國(guó)和親驶冒。 傳聞我的和親對(duì)象是個(gè)殘疾皇子苟翻,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,055評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容