摘要:文件系統(tǒng)請求和相關(guān)請求都會放進(jìn)這個線程池處理其他的請求,如網(wǎng)絡(luò)平臺特性相關(guān)的請求會分發(fā)給相應(yīng)的系統(tǒng)處理單元參見設(shè)計概覽。
譯者按:
在 Medium 上看到這篇文章,行文脈絡(luò)清晰,闡述簡明利落,果斷點下翻譯按鈕。
第一小節(jié)背景鋪陳略啰嗦,可以略過。剛開始我給這部分留了個 blah blah blah 直接翻后面的,翻完之后回頭看,考慮完整性才把第一節(jié)給補(bǔ)上。接下來的內(nèi)容干貨滿滿,相信對 Node.js 運行機(jī)制有興趣的讀者一定會有所收獲。
原文:Architecture of Node.js’ Internal Codebase
作者:Aren Li
首先,說點兒 JavaScript……
StackOverflow 的聯(lián)合創(chuàng)始人 Jeff Atwood 在他著名的編程博客 Coding Horror 上說:
any application that can be written in JavaScript, will eventually be written in JavaScript.
任何可以用 JavaScript 寫就的應(yīng)用程序,最終都會以 JavaScript 寫出來。
JavaScrit 的邊界和影響力在過去幾年里迅猛發(fā)展,現(xiàn)在已經(jīng)是最流行的編程語言之一。2016 年爆棧網(wǎng)的開發(fā)者調(diào)查中,JavaScript 在最流行技術(shù)和最熱門問答兩項排名第一,其他方面也名列前茅。
Node.js 是一個服務(wù)器端 JavaScript 執(zhí)行環(huán)境,提供了底層服務(wù)器功能環(huán)境,包括二進(jìn)制數(shù)據(jù)操作、文件系統(tǒng) I/O、數(shù)據(jù)庫訪問、網(wǎng)絡(luò)訪問等。它獨一無二的特性使其在現(xiàn)存的多種成熟服務(wù)器語言中脫穎而出,并且經(jīng)過了業(yè)界領(lǐng)先的科技公司如 Paypal、Tinder、Medium(是的,本文原文的那個博客系統(tǒng))、LinkedIn 和 Netflex 的實戰(zhàn)應(yīng)用,甚至這些都發(fā)生在 Node.js 發(fā)布 1.0 之前。
我最近在 StackOverflow 上回答一個關(guān)于 Node.js 內(nèi)部代碼結(jié)構(gòu)的問題,因此而萌生了寫作本文的念頭。
Node.js 的官方文檔其實講得并不清楚它是什么:
一個基于 Chrome V8 引擎的 JavaScript 運行時。Node.js 采用事件驅(qū)動、非阻塞 I/O 模型……
要理解這段話和它背后的真正力量,我們需要把 Node.js 拆分到組件,了解它們的關(guān)鍵技術(shù),如何交互協(xié)作,最終構(gòu)成了 Node.js 這個強(qiáng)大的運行時環(huán)境:
組件和第三方依賴V8:Google 開源的高性能 JavaScript 引擎,以 C++ 實現(xiàn)。這也是集成在 Chrome 中的 JS 引擎。V8 將你寫的 JavaScript 代碼編譯為機(jī)器碼(所以它超級快)然后執(zhí)行。V8 有多快?看看這個爆棧網(wǎng)的回答。
libuv:提供異步功能的 C 庫。它在運行時負(fù)責(zé)一個事件循環(huán)(Event Loop)、一個線程池、文件系統(tǒng) I/O、DNS 相關(guān)和網(wǎng)絡(luò) I/O,以及一些其他重要功能。
其他 C/C++ 組件和庫:如 c-ares、crypto (OpenSSL)、http-parser 以及 zlib。這些依賴提供了對系統(tǒng)底層功能的訪問,包括網(wǎng)絡(luò)、壓縮、加密等。
應(yīng)用/模塊(Application/Modules):這部分就是所有的 JavaScript 代碼:你的應(yīng)用程序、Node.js 核心模塊、任何 npm install 的模塊,以及你寫的所有模塊代碼。你花費的主要精力都在這部分。
綁定(Bindings):Node.js 用了這么多 C/C++ 的代碼和庫,簡單來說,它們性能很好。不過,JavaScript 代碼最后是怎么跟這些 C/C++ 代碼互相調(diào)用的呢?這不是三種不同的語言嗎?確實如此,而且通常不同語言寫出來的代碼也不能互相溝通,沒有 binding 就不行。Binding 是一些膠水代碼,能夠把不同語言綁定在一起使其能夠互相溝通。在 Node.js 中,binding 所做的就是把 Node.js 那些用 C/C++ 寫的庫接口暴露給 JS 環(huán)境。這么做的目的之一是代碼重用:這些功能已經(jīng)有現(xiàn)存的成熟實現(xiàn),沒必要只是因為換個語言環(huán)境就重寫一遍,如果橋接調(diào)用一下就足夠的話。另一個原因是性能:C/C++ 這樣的系統(tǒng)編程語言通常都比其他高階語言(Python、JavaScript、Ruby 等等)性能更高,所以把主要消耗 CPU 的操作以 C/C++ 代碼來執(zhí)行更加明智。
C/C++ Addons:Binding 僅橋接 Node.js 核心庫的一些依賴,zlib、OpenSSL、c-ares、http-parser 等。如果你想在應(yīng)用程序中包含其他第三方或者你自己的 C/C++ 庫的話,需要自己完成這部分膠水代碼。你寫的這部分膠水代碼就稱為 Addon??梢园?Binding 和 Addon 視為連接 JavaScript 代碼和 C/C++ 代碼的橋梁。
術(shù)語I/O:輸入/輸出(Input/Output)的縮寫,基本上代指那些主要由計算機(jī) I/O 子系統(tǒng)處理的操作。重 I/O 操作(I/O-bound operations)通常會牽涉到磁盤或驅(qū)動器訪問,例如數(shù)據(jù)庫訪問或文件系統(tǒng)相關(guān)操作。類似的概念還有重 CPU 操作(CPU-bound)、重內(nèi)存操作(Memory-bound)等等。它們的區(qū)分是根據(jù)系統(tǒng)哪部分性能對這個操作有最大的影響。比如對于某項操作而言,CPU 運算能力提高可以帶來最大的提升,這項操作就屬于重 CPU 操作。
非阻塞/異步:當(dāng)一項請求發(fā)來,應(yīng)用程序會處理這個請求,其他操作需要等這個請求處理完成才能執(zhí)行。這個流程的問題是:當(dāng)大量請求并發(fā)時每個請求都需要等待前一個完成,也就是說每個請求都會阻塞后面的所有請求,最糟糕的是如果前一個請求花了很長時間(比如從數(shù)據(jù)庫讀取 3GB 的數(shù)據(jù))后面所有請求都跟著悲劇了。解決辦法可以是引入多處理器和(或)多線程架構(gòu),這些辦法各有優(yōu)劣。Node.js 采用了另一種方式,不再為每個請求開啟一個新的線程,而是所有請求都在單一的主線程中處理,也只做這么一件事情:處理請求——請求中包含的 I/O 操作如文件系統(tǒng)訪問、數(shù)據(jù)庫讀寫等,都會轉(zhuǎn)發(fā)給由 libuv 管理的工作線程去執(zhí)行。也就是說,請求中的 I/O 操作是異步處理的,而非在主線程上進(jìn)行。這個辦法就使得主線程從不會阻塞,因為所有耗時的任務(wù)都分配到了別處。你需要面對的只有唯一的主線程,所有 libuv 管理的工作線程都與你隔離開來,無需操心,Node.js 會處理好那部分。在這個架構(gòu)之上重 I/O 操作變得格外高效,那些重 CPU、重內(nèi)存的也一樣。Node.js 提供了開箱即用的異步 I/O 調(diào)度,還有一些針對重 CPU 執(zhí)行的處理,不過這已經(jīng)超出本文話題范疇了。
事件驅(qū)動:基本上,所有現(xiàn)代系統(tǒng)都是主程序啟動完畢之后,對每個收到的請求開啟一個進(jìn)程,接下來根據(jù)不同技術(shù)有不同的處理方式,有時差異會大相徑庭。典型的實現(xiàn)是:針對一個請求開啟一個線程,一步接一步執(zhí)行任務(wù)操作,如果某個操作執(zhí)行緩慢,這個線程上的后續(xù)操作都會隨之掛起,直到所有操作完成,返回結(jié)果。而在 Node.js 中,所有的操作都注冊為一個事件,等待主程序或者外部請求來觸發(fā)。
(系統(tǒng))運行時:Node.js 運行時是指所有這些代碼(上述所有組件,包括底層和上層)提供給 Node.js 應(yīng)用程序執(zhí)行的環(huán)境。
合體我們已經(jīng)了解 Node.js 頂層組件各自的概貌,現(xiàn)在看看它們組合在一起的工作流程,可以更透徹地理解整體架構(gòu)以及各部分如何協(xié)作交互。
一個 Node.js 應(yīng)用啟動時,V8 引擎會執(zhí)行你寫的應(yīng)用代碼,保持一份觀察者(注冊在事件上的處理函數(shù))列表。當(dāng)事件發(fā)生時,它的處理函數(shù)會被加進(jìn)一個事件隊列。只要這個隊列還有等待執(zhí)行的事件,事件循環(huán)就會持續(xù)把事件從隊列中拿出,放進(jìn)調(diào)用堆棧。需要注意的是,只有當(dāng)前一個事件處理完畢(調(diào)用堆棧也已經(jīng)清空),事件循環(huán)才會把下一個事件放進(jìn)調(diào)用堆棧。
在調(diào)用堆棧中,所有的 I/O 請求都會轉(zhuǎn)發(fā)給 libuv 處理。libuv 會維持一個線程池,包含四個工作線程(這是默認(rèn)數(shù)量,也可以修改配置增加更多工作線程)。文件系統(tǒng) I/O 請求和 DNS 相關(guān)請求都會放進(jìn)這個線程池處理;其他的請求,如網(wǎng)絡(luò)、平臺特性相關(guān)的請求會分發(fā)給相應(yīng)的系統(tǒng)處理單元(參見 libuv 設(shè)計概覽)。
安排給線程池的這些 I/O 操作由 Node.js 的底層庫執(zhí)行,完成之后 libuv 把此事件放回事件隊列,等待主線程執(zhí)行后續(xù)操作。在 libuv 處理這些異步 I/O 操作期間,主線程不會等待處理結(jié)果,而是繼續(xù)忙其他事情,只有當(dāng)事件循環(huán)把 libuv 返回的事件放進(jìn)調(diào)用堆棧之后,主線程才會繼續(xù)處理這個事件的后續(xù)操作。這就是一個事件在 Node.js 中執(zhí)行的整個生命周期。
mbp 曾經(jīng)做過一個巧妙的比喻,把 Node.js 看成一家餐廳。我在此借用下他的例子,稍作修改來闡述下 Node.js 的執(zhí)行情況:
把 Node.js 應(yīng)用程序想象成一家星巴克,一個訓(xùn)練有素的前臺服務(wù)生(唯一的主線程)在柜臺前接受訂單。當(dāng)很多顧客同時光臨的時候,他們排隊(進(jìn)入事件隊列)等候接待;每當(dāng)服務(wù)生接待一位顧客,服務(wù)生會把訂單告知給經(jīng)理(libuv),經(jīng)理安排相應(yīng)的專職人員去烹制咖啡(工作線程或者系統(tǒng)特性)。這個專職人員會使用不同的原料和咖啡機(jī)(底層 C/C++ 組件)按訂單要求制作咖啡或甜點,通常會有四個這樣的專職人員保持在崗待命(線程池),高峰期的時候也可以安排更多(不過需要在一早就安排人員來上班,而不能中午臨時通知)。服務(wù)生把訂單轉(zhuǎn)交給經(jīng)理之后不需要等著咖啡制作完成,而是直接開始接待下一位顧客(事件循環(huán)放進(jìn)調(diào)用堆棧的另一個事件),你可以把當(dāng)前調(diào)用堆棧里的事件看成是站在柜臺前正在接受服務(wù)的顧客。
當(dāng)咖啡完成時,會被發(fā)送到顧客隊列的最后位置,等它移動到柜臺前服務(wù)生會叫相應(yīng)顧客的名字,顧客就來取走咖啡(最后這部分在真實生活中聽起來有點怪,不過你從程序執(zhí)行的角度理解就比較合乎情理了)。
以上就是 Node.js 的內(nèi)部頂層組件架構(gòu)概覽,以及它的事件循環(huán)機(jī)制。本文依然是非常精簡概括,還有很多問題和細(xì)節(jié)沒有展開,如重 CPU 操作的處理、Node.js 設(shè)計模式等,未來會有更多文章闡述這些內(nèi)容(譯注:在 Aren Li 的 Medium 專欄 Yet Another Node.js Blog 里)。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/79914.html
摘要:前端日報精選技術(shù)周刊譯文四種使用提升應(yīng)用的方式當(dāng)我們談?wù)撉岸思軜?gòu)時,我們到底在談?wù)撌裁词堑?,來了與之爭發(fā)布中文譯是的,來了掘金第期實踐總結(jié)個必備的裝逼技巧掘金年學(xué)習(xí)最好的書籍圓形隨機(jī)分布種事件驅(qū)動的架構(gòu)試用知識總結(jié)個人文章 2017-07-14 前端日報 精選 SegmentFault 技術(shù)周刊【譯文】四種使用webpack提升Vue應(yīng)用的方式當(dāng)我們談?wù)撉岸思軜?gòu)時,我們到底在談?wù)撌裁矗?..
摘要:本書的這一部分將為隨后的章節(jié)打下基礎(chǔ),會涵蓋模板,模塊化,和依賴注入。本書的小例子中我們會使用未經(jīng)壓縮的,開發(fā)友好的版本,在的上。作用域也可以針對特定的視圖來擴(kuò)展數(shù)據(jù)和特定的功能。 上一篇:【譯】《精通使用AngularJS開發(fā)Web App》(一) 下一篇:【譯】《精通使用AngularJS開發(fā)Web App》(三) 原版書名:Mastering Web Application D...
原文 先說1.1總攬: Reactor模式 Reactor模式中的協(xié)調(diào)機(jī)制Event Loop Reactor模式中的事件分離器Event Demultiplexer 一些Event Demultiplexer處理不了的復(fù)雜I/O接口比如File I/O、DNS等 復(fù)雜I/O的解決方案 未完待續(xù) 前言 nodejs和其他編程平臺的區(qū)別在于如何去處理I/O接口,我們聽一個人介紹nodejs,總是...
摘要:全文為這些年,我曾閱讀深入理解過或正在閱讀學(xué)習(xí)即將閱讀的一些優(yōu)秀經(jīng)典前端后端書籍。當(dāng)然,如果您喜歡這篇文章,可以動手點點贊或者收藏。 全文為這些年,我曾閱讀、深入理解過(或正在閱讀學(xué)習(xí)、即將閱讀)的一些優(yōu)秀經(jīng)典前端/Java后端書籍。全文為純原創(chuàng),且將持續(xù)更新,未經(jīng)許可,不得進(jìn)行轉(zhuǎn)載。當(dāng)然,如果您喜歡這篇文章,可以動手點點贊或者收藏。 基礎(chǔ) 基礎(chǔ)書籍 進(jìn)階 進(jìn)階階段,深入學(xué)習(xí)的書...
閱讀 1170·2019-08-30 12:44
閱讀 676·2019-08-29 13:03
閱讀 2588·2019-08-28 18:15
閱讀 2451·2019-08-26 10:41
閱讀 3124·2019-08-26 10:28
閱讀 3063·2019-08-23 16:54
閱讀 2015·2019-08-23 15:16
閱讀 844·2019-08-23 14:55