為什麼要用Node.js

瘋狂的技術宅發表於2019-05-30
翻譯:瘋狂的技術宅

原文:https://medium.com/the-node-j...

未經許可,禁止轉載!


本文首發微信公眾號:前端先鋒
歡迎關注,每天都給你推送新鮮的前端技術文章


介紹

JavaScript 的日益發展帶來了很多變化,當今的 Web 開發面貌已經變得截然不同。在幾年前是很難想象在伺服器上執行 JavaScript 的。

在深入研究Node.js之前,你可能想了解使用跨棧的 JavaScript 有什麼好處,它統一了語言和資料格式(JSON),允許你以最佳的方式重用開發人員資源。將 Node.js 合併到技術棧中是一個關鍵優勢。

Node.js 是一個基於 Chrome 的名為 V8 的 JavaScript 引擎構建的 JavaScript 執行環境。值得注意的是,Node.js 的建立者 Ryan Dahl 的“受到 Gmail 等應用的啟發”,目標是為了開發一個具有實時推送功能的網站。在 Node.js 中,他提供了一個用於處理非阻塞事件驅動的 I/O 工具。

用一句話來概括:Node.js 在基於websockets 推送技術的實時 Web 應用中大放異彩。在過去的 20 多年來我們一直在使用基於無狀態請求 - 響應模式的無狀態 Web 應用,現在終於擁有了能夠實時雙向連線的 Web 應用,其中客戶端和伺服器都可以啟動通訊,並允許它們自由地交換資料。

這與典型的總是由客戶端發起通訊的 Web 響應模式形了成鮮明的對比。此外它也同樣基於在標準埠 80 上執行的開放 Web 技術棧(HTML,CSS和JS)。

有人可能會爭辯說,我們多年來一直以 Flash 和 Java Applet 的形式做到這一點 —— 但實際上,這些只是使用 Web 作為傳輸協議將資料傳給客戶端的沙盒環境。此外,它們是隔離執行的,通常在非標準埠上執行,這可能需要額外的許可權。

憑藉其優勢,Node.js 在依賴其獨特優勢的眾多知名公司的技術堆疊中發揮著關鍵作用。 Node.js 基金會幾乎已經整合了所有最好的想法,可以在 Node.js 基金會的案例研究頁面上找到關於為什麼企業應該考慮 Node.js 的簡短PPT。

在本文中,我將不僅要討論如何使用這些優勢,而且還要討論 為什麼 你可能想要使用 Node.js ,並用一些經典的 Web 應用程式模型作為示例。

它是如何工作的?

Node.js 的主要思想是:在面向跨分散式裝置執行的資料密集型的實時程式時,使用非阻塞、事件驅動的 I/O 來保證輕量和高效。

這讀起來很拗口。

這意味著 Node.js 不是 一個即將成為主宰 Web 開發界的能夠解決一切的新平臺。 相反,它是一個滿足特定需求的平臺。理解這一點絕對是有必要的。你絕不希望將 Node.js 用於 CPU 密集型的操作;實際上,將它用於進行大量繁重運算的場合將會消除它幾乎所有的優點。 Node.js 真正發揮作用的地方在於構建快速、可擴充套件的網路應用,因為它能夠以高吞吐量處理大量併發連線,這相當於具有高可擴充套件性。

其底層的工作原理非常有趣。傳統的 Web 服務技術每個連線(請求)都會產生一個新執行緒,佔用系統記憶體並最終受限於可用的最大記憶體,而 Node.js 在單執行緒上執行,使用非阻塞 I/O 呼叫,允許它支援數以萬計的併發連線(在 event loop 中維持)。

clipboard.png

快速計算:假設每個執行緒需要 2 MB 記憶體,那麼在有 8 GB 記憶體的系統上執行的話,理論上最多有 4000 個併發連線(計算來自 Michael Abernethy 的文章 “Just what is Node.js?“,2011年在 IBM developerWorks 上釋出;不幸的是,這篇文章的連結現在已經失效了),這還沒有算上執行緒之間的上下文切換的成本。這就是你通常在傳統的 Web 伺服器技術中處理的場景。通過避免所有這些問題,Node.js 實現了超過 1M 連線併發數的級別,以及 600k 的 websockets 併發連線數

當然,編寫 Node.js 應用的潛在缺陷是存在客戶端請求之間共享單個執行緒的問題。首先,繁重的計算可能會阻塞 Node 的單個執行緒並導致 所有 客戶端出現問題(稍後會詳細說明),因為傳入的請求將被阻塞,直到計算完成為止。其次開發人員需要 非常小心,不要讓異常冒泡到到核心(最頂層)Node.js 事件迴圈,這將導致 Node.js 例項終止(程式崩潰)。

為了避免異常冒泡到頂層,常用技術是將錯誤作為回撥引數傳遞迴呼叫者(而不是像在其他環境中那樣丟擲它們)。即使一些未被處理的異常冒泡到頂層,也有一些工具來監視 Node.js 程式並執行必要的恢復崩潰 (雖然可能無法恢復到使用者會話的當前狀態),最常見的是 Forever 模組

npm:node 包管理器

在討論 Node.js 時,一件絕對不應該被忽略的事是支援使用內建的 npm 工具進行包管理,預設情況下每個 Node.js 環境都會安裝。 npm 模組的概念非常類似於 Ruby Gems:一組可通過線上儲存庫輕鬆安裝,具有版本和依賴關係管理的可重用元件,。

可以在 npm 網站上找到已打包模組的完整列表,也可以使用自動與 Node.js 一起安裝的 npm CLI 工具進行訪問。模組生態系統對所有人開放,任何人都可以釋出自己的模組,釋出的模組將出現在 npm 儲存庫中。有關 npm 的簡介,請參閱初學者指南,以及 npm 釋出教程中關於釋出模組的部分。

一些很有用的 npm 模組是:

  • express —— Express.js,一個受 Sinatra 啟發的 Node.js Web 開發框架,當今大多數 Node.js 應用程式的事實標準。
  • hapi —— 一個模組化的且非常易於使用的以配置為中心的框架,用於構建 Web 和服務應用
  • connect —— Connect 是 Node.js 的可擴充套件 HTTP 伺服器框架,提供了一系列稱為中介軟體的高效能“外掛”作為Express的基礎。
  • socket.iosockjs —— 今天最常見的兩個 websockets 伺服器端元件。
  • pug(以前叫 Jade)—— 受 HAML 啟發的流行模板引擎之一,Express.js 中的預設選項。
  • mongodbmongojs —— MongoDB 包裝器,為 Node.js 中的 MongoDB 物件資料庫提供 API。
  • redis —— Redis 客戶端。
  • forever —— 可能是確保給定 node 指令碼連續執行的最常用實用程式。在遇到意外故障時,將 Node.js 的程式保持在生產狀態。
  • bluebird —— 功能齊全的 Promises/A+ 實現,效能非常出色
  • moment —— 用於解析、驗證、操作和格式化日期的輕量級 JavaScript 日期庫。

列表還在不斷增長。那裡有很多有用的包,可供所有人使用。

哪些場合應該使用 Node.js

線上聊天

線上聊天是最典型的實時多使用者應用,也是 Node.js 的最佳案例:它是一個輕量級、高流量、資料密集型(但是低處理和計算)的應用程式,可分散式跨裝置執行。它也是一個很好的學習案例,因為它很簡單,但涵蓋了你在典型的 Node.js 程式中所使用的大部分範例。

讓我們試著描繪它是如何工作的。

假設一個最簡單的場景,在我們的網站上有一個聊天室,人們可以通過一對多(實際上是對所有人)的方式交換訊息。

在伺服器端,我們有一個簡單的 Express.js 程式,它實現了兩件事:1) 一個GET 請求的處理程式,它提供了包含留言板和用於初始化新訊息輸入的“傳送”按鈕的功能,以及2) 用於偵聽 websocket 客戶端發出的新訊息的w ebsockets 伺服器。

在客戶端,我們有一個 HTML 頁面,其中設定了幾個處理程式,一個用於“傳送”按鈕的單擊事件,它接收輸入訊息並將其傳送到 websocket,另一個用於偵聽新的傳入訊息並顯示在 websockets 客戶端上(即伺服器希望客戶端顯示的其他使用者傳送的訊息)。

當其中一個客戶釋出訊息時,會發生以下情況:

  • 瀏覽器捕獲單擊“傳送”按鈕事件處理 JavaScript 程式,從輸入欄位(即訊息文字)中獲取值,並使用連線到我們伺服器的 websocket 客戶端發出 websocket 訊息(在網頁初始化時初始化) 。
  • websocket 連線的伺服器端元件接收訊息,並使用廣播方式將其轉發給所有其他的客戶端。
  • 所有客戶端都通過在網頁中執行的 websockets 客戶端元件接收新訊息。然後,他們通過將新訊息新增頁面上並更新。

clipboard.png

這是最簡單的例子。對於更強大的解決方案,你可以使用基於 Redis 的簡單快取。或者在更高階的解決方案中,可以用訊息佇列作為訊息路由,還可以實現更強大的傳遞機制,例如可以在連線丟失或在客戶端離線時儲存訊息。但無論你做出哪些改進,Node.js 仍將按照相同的基本原則執行:對事件做出反應,處理許多併發連線,並保持使用者體驗的流暢性。

物件資料庫頂層的 API

雖然 Node.js 的確很適合開發實時應用,但它也很適合從物件資料庫(例如MongoDB)公開資料。 JSON 儲存的資料允許 Node.js 在物件與儲存資料一致和沒有資料轉換的情況下良好的執行。

例如,如果你正在使用 Rails,那麼你需要從 JSON 轉換為二進位制模型,然後通過 HTTP 再將它們轉為 JSON 在 React.js 或 Angular.js 中使用 ,甚至可以用簡單的 jQuery AJAX 進行呼叫。使用 Node.js,你可以通過 REST API 直接公開你的 JSON 物件來供客戶端使用。此外,在從資料庫讀取或寫入時(如果你使用的是MongoDB),你無需擔心在 JSON 和其他任何內容之間進行轉換的問題。總之在客戶端、伺服器和資料庫中使用統一的資料序列化格式,可以避免多次轉換的麻煩。

佇列輸入

如果你收到了大量併發資料,那麼你的資料庫可能會成為瓶頸。如上所述,Node.js 可以輕鬆地自己處理併發連線。但是因為資料庫訪問是一種阻塞操作(在這種情況下),所以我們遇到了麻煩。解決方案是在資料真正寫入資料庫之前先確認客戶端的行為。

通過這種方法,系統可以在高負載下保持其響應性,這在客戶端不需要確認資料成功寫入時尤其有用。典型的例子包括:記錄或寫入使用者跟蹤資料時進行分批處理;以及最終一致性(經常在NoSQL世界中使用)可以接受的不需要立即作出反映的操作(例如更新 Facebook 上的“Likes”計數)。

資料通過某種快取或訊息佇列(例如,RabbitMQ,ZeroMQ)排隊,並通過單獨的資料庫批量寫入過程,或者由計算密集型後端服務進行消化,再寫入更好的能夠執行此類任務的平臺。類似的行為可以用其他語言或框架實現,但不能在相同的硬體上實現,以維持相同的高吞吐量。

clipboard.png

簡而言之:使用 Node,你可以將資料庫寫先入到一個地方,稍後再去處理它們,就像它們已經被成功處理一樣。

資料流

在更傳統的Web平臺中,HTTP 請求和響應被看作是孤立事件,實際上他們是流。可以在 Node.js 中使用這個性質來構建一些很酷的功能。例如檔案可以被一邊上傳一邊處理,因為資料通過流進入,我們可以實時的去處理它。這可以用於實時音訊視訊編碼,以及在不同資料來源的之間進行代理(參見下一部分)。

代理

把 Node.js 用作伺服器端代理是很容易的,它能夠以非阻塞方式處理大量的併發連線。這對於為代理不同響應時間的多個服務,或從多個源收集資料的場景特別有用。

例如以下場景:當伺服器端程式與第三方資源進行通訊時,會從不同的來源提取資料,或者將影象和視訊等資源儲存到第三方雲服務上。

儘管有專用代理伺服器,但是如果你沒有基礎的代理架構,或者你需要本地開發環境,那麼 Node 可能會對你有所幫助。

股票交易商的資料介面

讓我們回到應用程式。可以很容易地用實時網路解決方案取代的另一個例子是股票經紀人的交易軟體,它用於跟蹤股票價格、執行計算、技術分析以及建立圖表。

如果切換到基於 Web 的實時解決方案,經紀人將可以輕鬆切換工作站或工作場所。很快,我們可能會開始在佛羅里達州的海灘上看到它們......

應用監控儀表板

另一個常見的用例,其中 Node-with-web-socket 完全適合:跟蹤網站訪問者並對他們的互動進行實時的視覺化。你可以從使用者那裡實時收集統計資訊,甚至可以通過在訪問渠道中特定的點來開啟通訊渠道,並與訪問者進行有針對性的互動,這種方案可以在這裡找到: CANDDi

想象一下,如果你能夠實時瞭解訪問者所做的事情,你將如何改善你的業務呢?通過使用 Node.js 的實時雙向套接字,現在就可以做到了。

系統監控儀表板

在基礎設施方面,。比如想要為其使用者提供服務監控頁面的SaaS提供商(例如,GitHub狀態頁面)。通過 Node.js 事件迴圈,我們可以建立一個功能強大的基於 Web 的儀表板,以非同步方式檢查服務的狀態,並使用 websockets 將資料推送到客戶端。公司內部和公共服務的狀態都可以使用該技術得到實時報告。

注意:不要嘗試在 Node.js 中構建硬實時系統(即需要一致響應時間的系統)。對於那類應用程式,Erlang 可能是更好的選擇

哪些場合可以使用 Node.js

伺服器端 Web 應用

配合 Express.js 的 Node.js 也可在伺服器端建立經典 Web 應用。對於這種方法,有人支援也有人反對。以下是一些需要考慮的問題:

優點:

  • 如果你的程式沒有任何 CPU 密集型計算,可以用 Javascript 和物件儲存資料庫(如MongoDB)構建它,甚至可以在資料庫級別進行構建。這顯著的簡化了開發工作。
  • 爬蟲會收到一個能夠完全呈現的 HTML 響應,這比單頁應用或在 Node.js 上執行的 websockets 應用程式更能進行 SEO 。

缺點:

  • 任何 CPU 密集型計算都會阻止 Node.js 響應,因此執行緒平臺是一種更好的方法。
  • 將 Node.js 與關聯式資料庫放一起使用仍然非常困難(更多細節見下文)。如果你要對關係型資料庫進行操作,請並選擇 Rails、Django 或 ASP.Net MVC 等其他環境。

CPU 密集型計算的一種替代方法是建立一個可高度擴充套件的 MQ 支援環境,該環境具有後端處理功能,以使 Node 成為一個前臺“職員”,並以非同步方式處理客戶端請求。

什麼時候不應使用 Node.js

帶有關係型資料庫的伺服器端 Web 應用

例如,將 Node.js + Express.js 與 Ruby on Rails 進行比較,當涉及到關係資料訪問時,顯然後者更合適。

與其競爭對手相比,Node.js 的關係型資料庫工具仍然相當原始。另一方面,Rails 提供了開箱即用的資料訪問設定以及資料庫架構遷移支援工具,另外還有其他的 Gems。 Rails 及類似框架擁有成熟的且經過驗證的 Active RecordData Mapper 資料訪問層實現,如果你想要嘗試在純 JavaScript 中複製這些功能的話,那麼祝你好運。

不過,如果你真的傾向於用 JS 實現一切,請檢視 SequelizeNode ORM2

如果僅僅是把 Node.js 用作面向公眾的介面,同時用 Rails 後端訪問關聯式資料庫,這是可以的,而且這種方式並不罕見。

繁重的伺服器端計算與處理

當涉及到繁重的計算時,Node.js 並不是最好的平臺。你絕對不想用 Node.js 去構建一個 Fibonacci 計算伺服器。通常,任何 CPU 密集型操作都會通過事件驅動的非阻塞 I/O 模型來抵消 Node 提供的所有吞吐量優勢,因為當執行緒被數字運算佔用時,任何傳入請求都將被阻止。

正如前面所說的,Node.js 是單執行緒的,只使用一個CPU核心。在多核伺服器上新增併發性時,Node 核心團隊以 cluster module 的形式完成一些工作。你也可以很容易地在反向代理 nginx 的後面執行幾個 Node.js 伺服器例項。

如果使用群集,你仍然應該將所有繁重的計算放到在更合適的環境下編寫的後臺程式中,並使它們通過像 RabbitMQ 這樣的訊息佇列伺服器進行通訊。

即使你所有的後臺處理最初可能在同一伺服器上執行,這種方法也有可能實現非常高的可伸縮性。這些後臺處理服務可以輕鬆地被分發到單獨的工作伺服器,而無需對前置 Web 伺服器負載進行配置。

當然,你也可以在其他平臺上使用相同的方法,但是使用 Node.js,你可以獲得我們所討論的高 reqs/sec 吞吐量,因為每個請求都是一個非常快速有效的小任務。

結論

我們討論了 Node.js 從理論到實踐,從它的目標和抱負開始,並以其最佳點和陷阱結束。當人們遇到 Node 的問題時,它幾乎總是唄歸結為阻塞操作是所有邪惡的根源 —— 其中 99% 的直接原因是對 Node 的誤用。

請記住:不要用 Node.js 來解決計算擴充套件問題。它是為了解決 I/O 擴充套件問題而設計的,它做得確實很好

所以,如果你的應用不包含 CPU 密集型操作,也不訪問任何阻塞資源的話,可以利用 Node.js 的優勢,享受快速、可擴充套件的網路應用。


本文首發微信公眾號:前端先鋒

未經許可,禁止轉載!

歡迎掃描二維碼關注公眾號,每天都給你推送新鮮的前端技術文章

歡迎掃描二維碼關注公眾號,每天都給你推送新鮮的前端技術文章


歡迎繼續閱讀本專欄其它高贊文章:


相關文章