[譯] 爲什麼要對生產環境的 Node.js 使用反向代理?

原文: medium.com/intrinsic/w…php

早在 2012 年,PHP 和 Ruby on Rails 還是統治渲染 web 應用的服務器端技術。可是,一個大膽的競爭者在社區中攪起風暴。這項在當年的一篇博文中宣稱經過了百萬級併發鏈接測試的技術並不是其餘,正是 Node.js,其流行程度從那時至今也在穩定增加。前端

不像當時的大多數同類技術,Node.js 自帶一個 web server。擁有這個 server 意味着開發者能夠繞過諸如 php.ini 的種種配置文件以及一個包含了若干 .htaccess 文件的層級結構。內置的 web server 一樣提供了其餘便利,如處理上傳文件的能力或易於實現 WebSockets 等。node

由 Node.js 驅動的 web 應用天天都順暢地處理數以億計的請求。世界上大多數大公司都在某些方面被 Node.js 影響。說 Node.js 可用於生產環境確定是個保守的說法。可是,對於構建 Node.js 仍有一個適用的建議:nginx

不該直接把一個 Node.js 進程暴露到 web 上,而應該將其隱藏在一個反向代理背後。git

在咱們揭示緣由以前,先看看那是什麼吧。github

反向代理是什麼?

一個反向代理(reverse proxy)基本上就是一種用於接受請求並將它們轉發到某處的另外一個 HTTP server 上的特殊 web server,也會接受響應並轉發回給原始的請求者。web

反向代理一般不會精確地發送原始請求,典型的作法是會在某些方面對請求做出修改。例如,若是反向代理位於 www.example.org:80 並轉發請求到 ex.example.org:8080 的話,大概就是它重寫了原始的 Host header 以匹配目標地址。反向代理也可能經過其餘方式修改請求,好比清理一個有缺陷的請求或是在協議之間做出轉換。算法

一旦反向代理收到一個響應,可能也會對其進行某方面的轉換。經常使用的途徑一樣是修改 Host header 以匹配原始請求。後端

請求的 body 也能被修改。一種一般的修改是在響應時執行 gzip 壓縮。另外一種常見改變是當基礎服務只是 HTTP 時啓用 HTTPS 支持。api

反向代理也能夠將到來的請求分發給多個後端實例。若是一個服務暴露於 api.example.org,那麼一個反向代理能夠轉發請求到 api1.internal.example.orgapi2.internal.example.org 等處。

有不少種不一樣的反向代理。兩種更流行的分別是 NginxHAProxy。這兩種工具都可以執行 gzip 壓縮和增長 HTTPS 支持,它們也能很好地適用於其它領域。Nginx 更流行一些,而且也有諸如從文件系統運行靜態文件服務器等一些其它有益的能力,因此咱們將在本文中使用它做爲例子。

如今咱們知道 何爲 反向代理了,下面來看看 爲什麼 咱們要將其用於 Node.js。

爲什麼應該使用一個反向代理?

SSL 終端

SSL 終端是使用反向代理的最主要緣由之一。將一個應用的協議從 http 變爲 https 可不止添加一個 s 字母那麼簡單。Node.js 自己 可以https 執行必要的加密和解密,也能被配置爲讀取必要的證書文件。

可是,配置一個用於和咱們的應用通訊的協議,並管理一個永不過時的 SSL 憑證,並不是真的是咱們的應用須要關注的事情。檢查一個代碼庫中的證書不僅是麻煩,同時也是一種安全風險。在應用啓動時從特定位置獲取證書也有其風險。

有鑑於此,最好在應用以外執行 SSL 終端,一般就由一個反向代理來承擔。受惠於像 certbot 這樣的技術,經過 Nginx 維護證書也像設置一個定時任務同樣簡單。這樣的一個任務能夠自動安裝新證書並動態重配置 Nginx 進程。與重啓每一個 Node.js 應用實例相比,這是一個破壞性小得多的過程。

同時,經過容許一個反向代理來執行 SSL 終端,這也意味着 只有 被反向代理的做者編寫的代碼能夠訪問你的私有 SSL 證書。反之,若是由你的 Node.js 應用處理 SSL,那麼你的應用用到的每個單獨的第三方模塊 -- 即使是潛在的惡意模塊,都能訪問你的私有 SSL 證書了。

gzip 壓縮

gzip 是另外一個你應該由應用讓渡到反向代理的特性。gzip 壓縮策略最好在管理級別設置,而不是不得不爲每一個應用指定和配置。

最好使用某些邏輯來決定對什麼採用 gzip。例如,很小的文件,或許比 1kb 還小的,或許就不值得壓縮,由於其 gzip 壓縮版本沒準會變得更大,亦或客戶端對其解壓的 CPU 開銷也更不划算。同時,當處理二進制數據時,取決於其格式可能也沒法從壓縮中獲益。有時 gzip 也沒法被簡單地啓用或禁用,爲了兼容壓縮算法須要檢查接收到的 Accept-Encoding header。

集羣化

JavaScript 是一種單線程語言,相應的,Node.js 自然也是一種單線程的服務器平臺(儘管 Node.js v10 中開始出現的實驗性 worker 線程支持致力於改變這一點)。這意味着要從一個 Node.js 應用中獲取儘量更大的吞吐量須要運行和 CPU 核數差很少相同的實例數量。

Node.js 自帶的 cluster 模塊能夠實現集羣化。收到的 HTTP 請求將會被交給一個 master 進程然後再被分發給 cluster workers。

可是,動態伸縮 cluster workers 會有點費勁。一般也會經過運行一個額外的 Node.js 進程做爲分發主進程來增長吞吐量。可是,跨機器伸縮進程對於 cluster 來講仍是有點強人所難了。

基於這些緣由,有時使用一個反向代理來分發正在運行的 Node.js 進程會更好。反向代理能被動態配置以指向新出現的應用進程。說實在的,一個應用就應該只關注其自身的工做,而不是管理多個拷貝並分發請求。

企業路由

當着手於大型 web 應用,特別是被有多個團隊的企業建立的應用時,使用一個反向代理來決定如何轉發請求是很是有用的。例如,指向 example.org/search/* 的請求可被路由到內部搜索應用,而其它指向 example.org/profile/* 的請求能夠被分發到內部資料應用上。

這樣的加工處理提供了其它強大的特性,如粘滯會話、藍/綠部署、A/B測試等等。我我的就曾在這樣一個由應用執行這些邏輯的代碼庫中工做,這種實現方式讓應用極難維護。

性能收益

Node.js 是高可塑性的。它能夠從文件系統架設靜態資源服務、對 HTTP 響應執行 gzip 壓縮、內建支持 HTTPS,另有不少其它特性。它甚至有能力經過 cluster 模塊,運行一個應用的多個實例並分發其自身的請求。

然而,基本上讓一個反向代理來處理這些操做,而不是靠 Node.js 應用去作,纔是符合咱們利益的。除了以上列出的緣由以外,另外一個想要在 Node.js 以外作這些操做的緣由是因爲效率。

SSL 加密和 gzip 壓縮是兩個高計算密集型的操做。專用型反向代理工具,如 Nginx 和 HAProxy,對這些操做術業有專攻,執行速度要快於 Node.js。用 Nginx 這樣的 web server 從磁盤上讀取靜態內容也比 Node.js 來得快。有時甚至比起用額外的 Node.js 進程來執行集羣化,用 Nginx 反向代理實現的效率都更高,內存和 CPU 的佔用都更少。

可是,耳聽爲虛。讓咱們運行一些基準測試!

如下負載測試是使用 siege (譯註:一個 http/https 迴歸測試和基準測試工具)執行的。咱們用併發值 10 運行命令(模擬用戶的請求),而且該命令會迭代運行 2 萬次(整體會有 20 萬次請求)。

爲檢驗內存使用量咱們在基準測試期間運行命令 pmap <pid> | grep total 若干次並取 平均值 做爲結果(譯註:Linux 中的 pmap 命令用於查看進程用了多少內存)。當使用單個 worker 線程運行 Nginx 時,最終會運行兩個實例,一個是主線程,另外一個是 worker 線程;而後將兩個值相加。當運行一個包含 2 個節點的 Node.js 集羣時,將有 3 個進程,一個是主進程,另兩個是 worker 進程。下表中的 approx memory 列是給定測試中每一個 Nginx 和 Node.js 進程的總和。

基準測試的結果

node-cluster 基準測試中咱們使用了 2 個 worker,這意味着共有 3 個 Node.js 進程在運行:1 個 master 和 2 個 worker。在 nginx-cluster-node 基準測試中咱們有 2 個 Node.js 進程在運行。每一個 Nginx 測試都有一個單獨的 Nginx 主進程和一個單獨的 Nginx worker 進程。基準測試包括從磁盤讀取一個文件,且不管是 Nginx 仍是 Node.js 都被配置爲將文件緩存在內存中。

使用 Nginx 爲 Node.js 執行 SSL 終端帶來了約 16% (746rps 到 865rps) 的吞吐量增加。使用 Nginx 執行 gzip 壓縮則讓吞吐量增長了約 50% (5,047rps 到 7,590rps)。使用 Nginx 管理一個進程集羣形成了約 1% (8,006rps 到 7,908rps) 的性能損失,大概是歸因於在迴環網絡設備間傳遞額外請求的開銷吧。

一個基本的 Node.js 單進程單內存使用量是約 600MB,而 Nginx 進程的約 50MB。這些使用量會根據使用了那些特性而小幅波動,例如,Node.js 使用了額外的約 13MB 來執行 SSL 終端,以及 Nginx 使用了額外的 4MB(譯註:652 - 601 - 46.1)來做爲 Node.js 的反向代理並架設從文件系統讀取靜態內容的服務。能夠注意到一個有趣的事情是 Nginx 在其生命週期中保持了穩定的內存吞吐量;然而 Node.js 的內存吞吐量因爲 JavaScript 自然的垃圾回收機制而時常起伏。

如下是執行這次基準測試的軟件版本:

  • Nginx: 1.14.2
  • Node.js: 10.15.3
  • Siege: 3.0.8

測試執行在一臺有 16GB 內存的機器上,有一塊 i7-7500U CPU 4x2.70GHz,Linux 內核爲 4.19.10。項目地址位於:github.com/IntrinsicLa…

簡化應用代碼

基準測試全面又出彩,但從個人觀點來講,將部分工做從 Node.js 應用讓渡給反向代理帶來的最大收益是代碼的簡化。咱們得以減小了大量可能形成潛在 bug 的必須應用代碼並將其換成了聲明式的配置。開發者中的一個廣泛觀點是他們對於讓 Nginx 等外部團隊編寫這部分代碼,而不是由他們本身編寫更有信心。

不一樣於要安裝和管理 gzip 壓縮中間件並在多個 Node.js 項目中保持其最新,咱們能夠在一處統一配置它。和加載 SSL 證書後再重啓應用進程不一樣,咱們可使用已有的證書管理工具。與其在應用中添加條件語句檢查進程是 master 仍是 worker,不如將其交給其它工具判斷。

反向代理容許咱們的應用聚焦於業務邏輯並忽略協議和進程管理。


儘管 Node.js 擁有運行在生產環境的完美能力,但將反向代理和生產環境的 HTTP Node.js 應用結合使用帶來了種種收益。像 SSL 和 gzip 這樣的操做變得更快。管理 SSL 證書也會更簡單。大量所需應用代碼也被減小了。



--End--

查看更多前端好文
請搜索 fewelife 關注公衆號

轉載請註明出處

相關文章
相關標籤/搜索