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