摘要:前言這將是一個分為兩部分,內容是關于在生產環境下,跑應用的最佳實踐。第一部分會關注安全性,第二部分則會關注性能和可靠性。關于第一部分,請參閱在生產環境下的最佳實踐安全性。
前言
這將是一個分為兩部分,內容是關于在生產環境下,跑Express應用的最佳實踐。第一部分會關注安全性,第二部分則會關注性能和可靠性。當你讀這篇文章時,會假設你已經對Node.js和web開發有所了解,并且對生產環境有了概念。
關于第一部分,請參閱Express在生產環境下的最佳實踐 - 安全性。
概覽正如第一部分所說,生產環境是供你的最終用戶們所使用的,而開發環境則是供你開發和測試代碼所用。故對于和兩個環境的要求,是非常不同的。例如,在開發環境下,你不必考慮伸縮性和可靠性還有性能的問題,但這些在生產環境下都非常重要。
接下來,我們會將此文分為兩大部分:
需要對代碼做的事,即開發部分。
需要對環境做的事,即運維部分,
需要對代碼做的事為了提升你應用的性能,你可以通過:
使用gzip壓縮
禁止使用同步方法
使用中間件來提供靜態文件
適當地打印日志
合理地處理異常
使用gzip壓縮Gzip壓縮可以顯著地減少你web應用的響應體大小,從而提升你的web應用的響應速度。在Express中,你可以使用compression中間件來啟用gzip:
var compression = require("compression"); var express = require("express"); var app = express(); app.use(compression());
對于在生產環境中,流量十分大的網站,最好是在反向代理層處理壓縮。如果這樣做,那么就不就需要使用compression了,而是需要參閱Nginx的ngx_http_gzip_module模塊的文檔。
禁止使用同步方法同步方法會在它返回之前都一直阻塞線程。一次多帶帶的調用可能影響不大,但在流量非常巨大的生產環境中,它是不可接受的,可能會導致嚴重的性能問題。
雖然大多數的Node.js和其第三方庫都同時提供了一個方法的同步和異步版本,但在生產環境下,請總是使用它的異步版本。唯一可能例外的場景可能是,如果這個方法只在應用初始化時調用一次,那么使用它的同步版本也是可以接受的。
如果你使用的是Node.js 4.0+ 或 io.js 2.1.0+ ,你可以在啟動應用時附上--trace-sync-io參數來檢查你的應用中哪里使用了同步API。更多關于這個參數的信息,你可以參閱io.js 2.1.0的更新日志。
使用中間件來提供靜態文件在開發環境下,你可以使用res.sendFile()來提供靜態文件。但在生產環境下,這是不被允許的,因為這個方法會在每次請求時都會對文件系統進行讀取。res.sendFile()并不是通過系統方法sendfile實現的。
對應的,你可以使用serve-static中間件來為你的Express應用提供靜態文件。
更好的選擇則是在反向代理層上提供靜態文件。
適當地打印日志總得來說,為你的應用打印日志的目的有兩個:調試和操作記錄。在開發環境下,我們通常使用console.log()或console.err()來做這些事。但是,當這些方法的輸出目標是終端或文件時,它們是同步的,所以它們并不適用于生產環境,除非你將輸出導流至另一個程序中。
如果你正在為了調試而打印日志。那么你可以使用一些專用于調試的庫如debug,用于代替console.log()。這個庫可以通過設置DEBUG環境變量來控制具體哪些信息會被打印。雖然這些方法也是同步的,但你一定不會在生產環境下進行調試吧?
為了操作記錄如果你正在為了記錄應用的活動而打印日志。那么你可以使用一些日志庫如winston或Bunyan,來替代console.log()。更多關于這兩個庫的詳情,可以參閱這里。
合理地處理異常Node.js在遇到未處理的異常時就會退出。如果沒有合理地捕獲并處理異常,這會使你的應用崩潰和離線。如果你使用了一個自動重啟的工具,那么你的應用則會在崩潰后立刻重啟,而且幸運的是,Express應用的重啟時間通常都很快。但是不管怎樣,你都想要盡量避免這種崩潰。
為了保證你合理處理異常,請遵從以下指示:
使用try-catch
使用promise
不應該做的事你不應該監聽全局事件uncaughtException。監聽該事件將會導致進程遇到未處理異常時的行為被改變:進程將會忽略此異常并繼續運行。這聽上去很好,但是如果你的應用中存在未處理異常,繼續運行它是非常危險的,因為應用的狀態開始變得不可預測。
所以,監聽uncaughtException并不是一個好主意,它已被官方地列為了不推薦的做法,并且以后可能會移除這個接口。我們更推薦的是,使用多進程和自動重啟。
我們同樣不推薦使用domains。它通常也并不能解決問題,并且已是一個被標識為棄用的模塊。
使用try-catchTry-catch是一個JavaScript語言自帶的捕獲同步代碼的結構。使用try-catch,你可以捕獲例如JSON解析錯誤這樣的異常。
使用JSHint或JSLint這樣的工具則可以讓你遠離引用錯誤或未定義變量這種隱式的異常。
一個使用try-catch來避免進程退出的例子:
// Accepts a JSON in the query field named "params" // for specifying the parameters app.get("/search", function (req, res) { // Simulating async operation setImmediate(function () { var jsonStr = req.query.params; try { var jsonObj = JSON.parse(jsonStr); res.send("Success"); } catch (e) { res.status(400).send("Invalid JSON string"); } }) });
但是,try-catch只能捕獲同步代碼的異常。但是Node.js世界主要是異步的,所以,對于大多數的異常它都無能為力。
使用promisePromise可以通過then()處理異步代碼里的一切異常(顯式和隱式)。記得在promise鏈的最后加上.catch(next)。例子:
app.get("/", function (req, res, next) { // do some sync stuff queryDb() .then(function (data) { // handle data return makeCsv(data) }) .then(function (csv) { // handle csv }) .catch(next) }) app.use(function (err, req, res, next) { // handle error })
現在所有的同步代碼和異步代碼的異常都傳遞到了異常處理中間件中。
但是,仍有兩點需要提醒:
所有你的異步代碼都必須返回一個promise(除了emitter)。如果你正在使用的庫沒有返回一個promise,那么就使用一些工具方法(如Bluebird.promisifyAll())來轉換它。Event emitter(如stream)仍會造成未處理的異常。所以你必須合理地監聽它們的error事件。例子:
app.get("/", wrap(async (req, res, next) =>; { let company = await getCompanyById(req.query.id) let stream = getLogoStreamById(company.id) stream.on("error", next).pipe(res) }))
更多關于使用promise處理異常的信息,請參閱這里。
需要對環境做的事以下是一些你可以對你的系統環境做的事,用于提升你應用的性能:
將NODE_ENV設置為“production”
保證你的應用在發生錯誤后自動重啟
使用集群模式運行你的應用
緩存請求結果
使用負載均衡
使用反向代理
將NODE_ENV設置為“production”NODE_ENV環境變量指明了應用當前的運行環境(開發或生產)。你可以做的為你的Express提升性能的最簡單的事情之一,就是將NODE_ENV設置為“production”。
將NODE_ENV設置為“production”將使Express:
緩存視圖模板
緩存CSS文件
生成更簡潔的錯誤信息
如果你想寫環境相關的代碼,你可以通過process.env.NODE_ENV來獲取運行時NODE_ENV的值。不過需要注意的,檢查環境變量的值會造成少許的性能損失,所以不要有太多這類操作。
你可能已經習慣了SHELL中設置環境變量,例如使用export或.bash_profile文件。但是你不應該在你的生產服務器上這么做。你應該使用操作系統的初始化系統(systemd或systemd)。下一個章節將會更詳細的講述初始化系統,但是由于設置NODE_ENV是如此的重要以及簡單,所以我們在這里就列出它:
當使用Upstart時,請在任務文件中使用env關鍵字。例子:
# /etc/init/env.conf env NODE_ENV=production
更多信息,請參閱這里。
當使用systemd時,請在你的單元文件中使用Environment指令。例子:
# /etc/systemd/system/myservice.service Environment=NODE_ENV=production
更多信息,請參閱這里。
如果你正在使用StrongLoop Process Manager,你也可以參閱這篇文章。
保證你的應用在發生錯誤后自動重啟在生產環境下,你一定不希望你的應用離線。所以你需要保證在你的應用發生錯誤時或你的服務器自身崩潰時,你的應用可以自動重啟。雖然你可能不期望它們的發生,但是我們需要更現實得預防它們,可以通過:
使用一個進程管理員(process manager)庫來重啟你的應用
當你的操作系統崩潰時,使用它提供的初始化系統來重啟你的進程管理員。
Node.js應用在遇到未處理異常時就會退出。你的首要任務是保證你的代碼的測試健全并且合理地處理了所有的異常。但是如有萬一,請準備一個機制來確保它的自動重啟。
使用進程管理員(process manager)在開發環境下,你可以簡單地使用node server.js這樣的命令來啟動你的應用。當時在生產環境下這么做將是不被允許的。如果應用崩潰了,在你手動重啟它之前,它都會處于離線狀態。為了保證你應用的自動重啟,請使用一個進程管理員,它可以幫助你管理正在運行的應用。
除了保證你的應用的自動重啟,一個進程管理員還可以使你:
獲取當前運行環境的性能表現和資源消耗情況。
自動地修改環境設置
管理集群(StrongLoop PM和pm2)
Node.js世界里比較流行的進程管理員有:
StrongLoop Process Manager
PM2
Forever
更多的它們之間的比較,你可以參閱這里。關于它們三者的簡介,你可以參閱這篇文章。
使用一個初始化系統接下來要保證的就是,在你的服務器重啟時,你的應用也會相應的重啟。盡管我們認為我們的服務器是十分穩定的,但它們仍有掛掉的可能。所以為了保證在你的服務器時重啟時你的應用也會重啟,請使用你操作系統內建的初始化系統。如今比較主流的是systemd和Upstart。
以下是通過你的Express應用來使用初始化系統的兩種方法:
將你的應用運行于一個進程管理員中,然后將進程管理員設置為系統的一個服務。這個是比較推薦的做法。
直接通過初始化系統運行你的應用。這個方法更為簡單,但你卻享受不到進程管理員帶來的福利。
SystemdSystems是一個linux系統的服務管理員。大多數的linux發行版都將它作為默認的初始化系統。
一個systems服務的配置文件也被稱為一個單元文件,有一個.service后綴。以下是一個直接管理Node.js應用的例子:
[Unit] Description=Awesome Express App [Service] Type=simple ExecStart=/usr/local/bin/node /projects/myapp/index.js WorkingDirectory=/projects/myapp User=nobody Group=nogroup # Environment variables: Environment=NODE_ENV=production # Allow many incoming connections LimitNOFILE=infinity # Allow core dumps for debugging LimitCORE=infinity StandardInput=null StandardOutput=syslog StandardError=syslog Restart=always [Install] WantedBy=multi-user.target
更多關于systemd的信息,請參閱這里。
UpstartUpstart是一個大多數linux發行版都可用的系統工具,用于在系統啟動時啟動任務和服務,在系統關閉時停止它們,并且監控它們。你可以先將你的Express應用或進程管理員配置為一個服務,然后Upstart會自動地在系統重啟后重啟它們。
一個Upstart服務被定義在一個任務配置文件中,有一個.conf后綴。下面的例子展示了如何創建一個名為“myapp”的任務,且應用的入口是/projects/myapp/index.js。
在/etc/init/下創建一個名為myapp.conf的文件:
# When to start the process start on runlevel [2345] # When to stop the process stop on runlevel [016] # Increase file descriptor limit to be able to handle more requests limit nofile 50000 50000 # Use production mode env NODE_ENV=production # Run as www-data setuid www-data setgid www-data # Run from inside the app dir chdir /projects/myapp # The process to start exec /usr/local/bin/node /projects/myapp/index.js # Restart the process if it is down respawn # Limit restart attempt to 10 times within 10 seconds respawn limit 10 10
注意:這個腳本要求Upstart 1.4 或更新的版本,支持于Ubuntu 12.04-14.10。
除了自動重啟你的應用,Upstart還為你提供了以下命令:
start myapp – 手動啟動應用
restart myapp – 手動重啟應用
stop myapp – 手動退出應用
更多關于Upstart的信息,請參閱這里。
使用集群模式運行你的應用在多核的系統里,你可以通過啟動一個進程集群來成倍了提升你應用的性能。一個集群運行了你的應用的多個實例,理想情況下,一個CPU核對應一個實例。這樣,便可以在多個實例件進行負載均衡。
值得注意的是,由于應用實例跑在不同的進程里,所以它們并不分享同一塊內存空間。因為,應用里的所有對象都是本地的,你不可以在應用代碼里維護狀態。不過,你可以使用如redis這樣的內存數據庫來存儲session這樣的數據和狀態。
在集群中,一個工作進程的崩潰不會影響到其他的工作進程。所以除了性能因素之外,多帶帶工作進程崩潰的相互不影響也是另一個使用集群的好處。一個工作進程崩潰后,請確保記錄下日志,然后重新通過cluster.fork()創建一個新的工作進程。
使用Node.js的cluster模塊Node.js提供了cluster模塊來支持集群。它使得一個主進程可以創建出多個工作進程。但是,比起直接使用這個模塊,許多的庫已經為你封裝了它,并提供了更多自動化的功能:如node-pm或cluser-service。
緩存請求結果另一個提升你應用性能的途徑是緩存請求的結果,這樣一來,對于同一個請求,你的應用就不必做多余的重復動作。
使用一個如Varnish或Nginx這樣的緩存服務器可以極大地提升你應用的響應速度。
使用負載均衡不論一個應用優化地多么好,一個多帶帶的實例總是有它的負載上限的。一個很好的解決辦法就是將你的應用跑上多個實例,然后在它們之前加上一個負載均衡器。
一個負載均衡器通常是一個反向代理,它接受負載,并將其均勻得分配給各個實例或服務器。你可以通過Nginx或HAProxy十分方便地架設一個負載均衡器。
使用了負載均衡后,你可以保證每個請求都根據它的來源被設置了獨特session id。當然,你也可以使用如Redis這樣的內存數據庫來存儲session。更多詳情,可以參閱這里。
負載均衡是一個相當復雜的話題,更加細致的討論已超過了本文的范疇。
使用反向代理一個反向代理被設置與web應用之前,用于支持各類對于請求的操作,如將請求發送給應用,自動處理錯誤頁,壓縮,緩存,提供靜態文件,負載均衡,等等。
在生產環境中,這里推薦將Express應用跑在Nginx或HAProxy之后。
最后原文鏈接:https://strongloop.com/strongblog/best-practices-for-express-in-production-part-two-performance-and-reliability/
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/86261.html
摘要:前言這將是一個分為兩部分,內容是關于在生產環境下,跑應用的最佳實踐。潛在的攻擊者可以通過它們進行針對性的攻擊。 前言 這將是一個分為兩部分,內容是關于在生產環境下,跑Express應用的最佳實踐。第一部分會關注安全性,第二部分最會關注性能和可靠性。當你讀這篇文章時,假設你已經對Node.js和web開發有所了解,并且對生產環境有了概念。 概覽 生產環境,指的是軟件生命循環中的某個階段。...
摘要:本文適合的讀者現在在手淘,京東,今日頭條,美柚等過億用戶的手機中的,都常見網頁,他們有更新快,靈活,便于分享和傳播的特性。這里有他們中的幾個的例子手淘,美柚。 本文適合的讀者??????? 現在在手淘,京東,今日頭條,美柚等過億用戶的手機app中的,都常見h5網頁,他們有更新快,靈活,便于分享和傳播的特性。這里有他們中的幾個h5的例子:(手淘,美柚)。這些app中都嵌者數以百計,千計的...
摘要:本文適合的讀者現在在手淘,京東,今日頭條,美柚等過億用戶的手機中的,都常見網頁,他們有更新快,靈活,便于分享和傳播的特性。這里有他們中的幾個的例子手淘,美柚。 本文適合的讀者??????? 現在在手淘,京東,今日頭條,美柚等過億用戶的手機app中的,都常見h5網頁,他們有更新快,靈活,便于分享和傳播的特性。這里有他們中的幾個h5的例子:(手淘,美柚)。這些app中都嵌者數以百計,千計的...
摘要:本文適合的讀者現在在手淘,京東,今日頭條,美柚等過億用戶的手機中的,都常見網頁,他們有更新快,靈活,便于分享和傳播的特性。這里有他們中的幾個的例子手淘,美柚。 本文適合的讀者??????? 現在在手淘,京東,今日頭條,美柚等過億用戶的手機app中的,都常見h5網頁,他們有更新快,靈活,便于分享和傳播的特性。這里有他們中的幾個h5的例子:(手淘,美柚)。這些app中都嵌者數以百計,千計的...
閱讀 2894·2021-08-20 09:37
閱讀 1618·2019-08-30 12:47
閱讀 1106·2019-08-29 13:27
閱讀 1694·2019-08-28 18:02
閱讀 758·2019-08-23 18:15
閱讀 3097·2019-08-23 16:51
閱讀 940·2019-08-23 14:13
閱讀 2159·2019-08-23 13:05