Node.js 問世後,前端的觸角逐漸蔓延到後端,甚至透過 Electron.js 製作完整的桌面 GUI 應用程式。藉由 Node.js,前端開發者以熟悉的 JavaScript 為敲門磚,逐步拓展技術守備範圍。但為網頁而生的語言為何能透過 Node.js 跑在伺服器端?要從認識 Node.js 出發。
Node.js#
Node.js 是什麼呢?根據官網的說法:
Node.js® is a JavaScript runtime built on Chrome’s V8 JavaScript engine.
「runtime」 指的是執行環境,就如同網頁上的 JavaScript 是在瀏覽器的 JavaScript 引擎上執行,Node.js 就是一個能執行 JavaScript 的環境,而 V8 引擎(V8 Engine)則是主流瀏覽器 - Google Chrome 的 JavaScript 引擎,負責解析、執行 JavaScript,也就是負責實踐 ECMAScript 規範中定義的部份;另外,V8 是開源的專案,有興趣的讀者可以參照 Google Git - V8。
Node.js 以 V8 為核心,加上一系列 C/C++ 的套件,成功的讓 Server 端也可以執行 JavaScript。
優點#
但是,後端語言已經這麼多了,為什麼還要大費周章的將 JavaScript 移植到 Server 端呢?
這是因為 JavaScript 是一個事件驅動的語言,透過事件迴圈(Event Loop),能讓執行緒幾乎不會被卡住;而這樣的特性,非常適合用來接收高併發(High Concurrency)的請求。
例如在傳統的伺服器中,每個使用者的連接都會產生一個新的執行緒(看實作,不一定),並佔據一定的效能,伺服器在高併發的情況下,很容易就會由於應接不暇而無法服務新的流量;但 Node.js 會將每個 request 變成事件迴圈中待處理的事件,主執行緒只負責承接、轉拋、回應,並持續的在事件迴圈中循環,一切都以事件為核心在驅動程式運行,自然也就不會出現執行緒卡死的現象。
當然,如果是商業邏輯複雜的後端程式,效能瓶頸不在流量的服務,Node.js 就無用武之地;但在設計需要承接高流量,且處理邏輯不太複雜時,Node.js 可能就會是個可以考慮的選項。
功能#
前面提到,Node.js 就是一個可以執行 JavaScript 的環境,而這個環境除了提供瀏覽器 Web API 實作的 setTimeout、setInterval、console 之外,也因為執行環境不同,有另外一系列的 API 供開發者使用,例如可以讀寫檔案的 fs、處理網路請求的 http、做加解密雜湊處理的 crypto、設定叢集的 cluster 等等。
詳細的使用說明,可以參考 Node.js 的官網文件。
事件迴圈#
由於 JavaScript 擁有單執行緒的特性,且為了讓執行緒不會被需要等待的同步程式卡住,必須透過事件迴圈的機制來實現這個目標。
瀏覽器中 JavaScript 的事件迴圈,其中有很大一部分是由瀏覽器完成的;在 Node.js 中則透過 libuv 來實現這部分的機制。不同於瀏覽器的事件迴圈,Node.js 中的事件迴圈大致會有以下幾個階段:
| 階段 | 職責 |
|---|---|
| timers | 執行 setTimeout、setInterval 給的 callback |
| pending callbacks | 執行被延遲到下一個事件迴圈的 I/O Callback |
| idle, prepare | Node.js 內部專用的階段 |
| poll | 檢索新的 I/O events,執行 I/O callbacks |
| check | 執行 setImmediate 給的 callback |
| close callbacks | 執行關閉資源的 callback,例如 socket.on('close', ...) |
相對於瀏覽器的事件迴圈多了好多個階段,但其實只是把所有的 callback 分成了四種:timers、I/O events、immediates、close handlers,並依照順序輪流執行,其他在概念上還是一樣的:每個階段有自己的 Queue,輪到它時清空 Queue,到下個階段,周而復始。
為避免主執行緒阻塞,poll 階段可以設定執行上限,到達上限時就會將 Queue 內的東西移交到 pending callbacks 階段的 Queue 中,下一個事件迴圈時再接續執行。
比較需要注意的地方是,微任務佇列(microtask queue)在每個階段結束後都會執行、清空,順序是先清空 process.nextTick 的 callback,再執行其他的如 Promise 的 callback。
常有人誤解 process.nextTick,會想問例如「一個 Tick 是多久?」之類的問題,但其實 Tick 指的就是事件迴圈中的一個階段,因此時間是不固定的喔!
結語#
本章從基本介紹出發,認識 Node.js 這個 Server 端 JavaScript 執行環境,討論其在高併發情況的優勢,並理解事件迴圈在伺服器端的運作方式。
原文出處#
- 原書/iThome:https://ithelp.ithome.com.tw/articles/10227273