摘要:前言這將是一個分為兩部分,內(nèi)容是關(guān)于在生產(chǎn)環(huán)境下,跑應(yīng)用的最佳實踐。第一部分會關(guān)注安全性,第二部分則會關(guān)注性能和可靠性。關(guān)于第一部分,請參閱在生產(chǎn)環(huán)境下的最佳實踐安全性。
前言
這將是一個分為兩部分,內(nèi)容是關(guān)于在生產(chǎn)環(huán)境下,跑Express應(yīng)用的最佳實踐。第一部分會關(guān)注安全性,第二部分則會關(guān)注性能和可靠性。當(dāng)你讀這篇文章時,會假設(shè)你已經(jīng)對Node.js和web開發(fā)有所了解,并且對生產(chǎn)環(huán)境有了概念。
關(guān)于第一部分,請參閱Express在生產(chǎn)環(huán)境下的最佳實踐 - 安全性。
概覽正如第一部分所說,生產(chǎn)環(huán)境是供你的最終用戶們所使用的,而開發(fā)環(huán)境則是供你開發(fā)和測試代碼所用。故對于和兩個環(huán)境的要求,是非常不同的。例如,在開發(fā)環(huán)境下,你不必考慮伸縮性和可靠性還有性能的問題,但這些在生產(chǎn)環(huán)境下都非常重要。
接下來,我們會將此文分為兩大部分:
需要對代碼做的事,即開發(fā)部分。
需要對環(huán)境做的事,即運維部分,
需要對代碼做的事為了提升你應(yīng)用的性能,你可以通過:
使用gzip壓縮
禁止使用同步方法
使用中間件來提供靜態(tài)文件
適當(dāng)?shù)卮蛴∪罩?/p>
合理地處理異常
使用gzip壓縮Gzip壓縮可以顯著地減少你web應(yīng)用的響應(yīng)體大小,從而提升你的web應(yīng)用的響應(yīng)速度。在Express中,你可以使用compression中間件來啟用gzip:
var compression = require("compression"); var express = require("express"); var app = express(); app.use(compression());
對于在生產(chǎn)環(huán)境中,流量十分大的網(wǎng)站,最好是在反向代理層處理壓縮。如果這樣做,那么就不就需要使用compression了,而是需要參閱Nginx的ngx_http_gzip_module模塊的文檔。
禁止使用同步方法同步方法會在它返回之前都一直阻塞線程。一次多帶帶的調(diào)用可能影響不大,但在流量非常巨大的生產(chǎn)環(huán)境中,它是不可接受的,可能會導(dǎo)致嚴(yán)重的性能問題。
雖然大多數(shù)的Node.js和其第三方庫都同時提供了一個方法的同步和異步版本,但在生產(chǎn)環(huán)境下,請總是使用它的異步版本。唯一可能例外的場景可能是,如果這個方法只在應(yīng)用初始化時調(diào)用一次,那么使用它的同步版本也是可以接受的。
如果你使用的是Node.js 4.0+ 或 io.js 2.1.0+ ,你可以在啟動應(yīng)用時附上--trace-sync-io參數(shù)來檢查你的應(yīng)用中哪里使用了同步API。更多關(guān)于這個參數(shù)的信息,你可以參閱io.js 2.1.0的更新日志。
使用中間件來提供靜態(tài)文件在開發(fā)環(huán)境下,你可以使用res.sendFile()來提供靜態(tài)文件。但在生產(chǎn)環(huán)境下,這是不被允許的,因為這個方法會在每次請求時都會對文件系統(tǒng)進行讀取。res.sendFile()并不是通過系統(tǒng)方法sendfile實現(xiàn)的。
對應(yīng)的,你可以使用serve-static中間件來為你的Express應(yīng)用提供靜態(tài)文件。
更好的選擇則是在反向代理層上提供靜態(tài)文件。
適當(dāng)?shù)卮蛴∪罩?/b>總得來說,為你的應(yīng)用打印日志的目的有兩個:調(diào)試和操作記錄。在開發(fā)環(huán)境下,我們通常使用console.log()或console.err()來做這些事。但是,當(dāng)這些方法的輸出目標(biāo)是終端或文件時,它們是同步的,所以它們并不適用于生產(chǎn)環(huán)境,除非你將輸出導(dǎo)流至另一個程序中。
如果你正在為了調(diào)試而打印日志。那么你可以使用一些專用于調(diào)試的庫如debug,用于代替console.log()。這個庫可以通過設(shè)置DEBUG環(huán)境變量來控制具體哪些信息會被打印。雖然這些方法也是同步的,但你一定不會在生產(chǎn)環(huán)境下進行調(diào)試吧?
為了操作記錄如果你正在為了記錄應(yīng)用的活動而打印日志。那么你可以使用一些日志庫如winston或Bunyan,來替代console.log()。更多關(guān)于這兩個庫的詳情,可以參閱這里。
合理地處理異常Node.js在遇到未處理的異常時就會退出。如果沒有合理地捕獲并處理異常,這會使你的應(yīng)用崩潰和離線。如果你使用了一個自動重啟的工具,那么你的應(yīng)用則會在崩潰后立刻重啟,而且幸運的是,Express應(yīng)用的重啟時間通常都很快。但是不管怎樣,你都想要盡量避免這種崩潰。
為了保證你合理處理異常,請遵從以下指示:
使用try-catch
使用promise
不應(yīng)該做的事你不應(yīng)該監(jiān)聽全局事件uncaughtException。監(jiān)聽該事件將會導(dǎo)致進程遇到未處理異常時的行為被改變:進程將會忽略此異常并繼續(xù)運行。這聽上去很好,但是如果你的應(yīng)用中存在未處理異常,繼續(xù)運行它是非常危險的,因為應(yīng)用的狀態(tài)開始變得不可預(yù)測。
所以,監(jiān)聽uncaughtException并不是一個好主意,它已被官方地列為了不推薦的做法,并且以后可能會移除這個接口。我們更推薦的是,使用多進程和自動重啟。
我們同樣不推薦使用domains。它通常也并不能解決問題,并且已是一個被標(biāo)識為棄用的模塊。
使用try-catchTry-catch是一個JavaScript語言自帶的捕獲同步代碼的結(jié)構(gòu)。使用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世界主要是異步的,所以,對于大多數(shù)的異常它都無能為力。
使用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 })
現(xiàn)在所有的同步代碼和異步代碼的異常都傳遞到了異常處理中間件中。
但是,仍有兩點需要提醒:
所有你的異步代碼都必須返回一個promise(除了emitter)。如果你正在使用的庫沒有返回一個promise,那么就使用一些工具方法(如Bluebird.promisifyAll())來轉(zhuǎn)換它。Event emitter(如stream)仍會造成未處理的異常。所以你必須合理地監(jiān)聽它們的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) }))
更多關(guān)于使用promise處理異常的信息,請參閱這里。
需要對環(huán)境做的事以下是一些你可以對你的系統(tǒng)環(huán)境做的事,用于提升你應(yīng)用的性能:
將NODE_ENV設(shè)置為“production”
保證你的應(yīng)用在發(fā)生錯誤后自動重啟
使用集群模式運行你的應(yīng)用
緩存請求結(jié)果
使用負載均衡
使用反向代理
將NODE_ENV設(shè)置為“production”NODE_ENV環(huán)境變量指明了應(yīng)用當(dāng)前的運行環(huán)境(開發(fā)或生產(chǎn))。你可以做的為你的Express提升性能的最簡單的事情之一,就是將NODE_ENV設(shè)置為“production”。
將NODE_ENV設(shè)置為“production”將使Express:
緩存視圖模板
緩存CSS文件
生成更簡潔的錯誤信息
如果你想寫環(huán)境相關(guān)的代碼,你可以通過process.env.NODE_ENV來獲取運行時NODE_ENV的值。不過需要注意的,檢查環(huán)境變量的值會造成少許的性能損失,所以不要有太多這類操作。
你可能已經(jīng)習(xí)慣了SHELL中設(shè)置環(huán)境變量,例如使用export或.bash_profile文件。但是你不應(yīng)該在你的生產(chǎn)服務(wù)器上這么做。你應(yīng)該使用操作系統(tǒng)的初始化系統(tǒng)(systemd或systemd)。下一個章節(jié)將會更詳細的講述初始化系統(tǒng),但是由于設(shè)置NODE_ENV是如此的重要以及簡單,所以我們在這里就列出它:
當(dāng)使用Upstart時,請在任務(wù)文件中使用env關(guān)鍵字。例子:
# /etc/init/env.conf env NODE_ENV=production
更多信息,請參閱這里。
當(dāng)使用systemd時,請在你的單元文件中使用Environment指令。例子:
# /etc/systemd/system/myservice.service Environment=NODE_ENV=production
更多信息,請參閱這里。
如果你正在使用StrongLoop Process Manager,你也可以參閱這篇文章。
保證你的應(yīng)用在發(fā)生錯誤后自動重啟在生產(chǎn)環(huán)境下,你一定不希望你的應(yīng)用離線。所以你需要保證在你的應(yīng)用發(fā)生錯誤時或你的服務(wù)器自身崩潰時,你的應(yīng)用可以自動重啟。雖然你可能不期望它們的發(fā)生,但是我們需要更現(xiàn)實得預(yù)防它們,可以通過:
使用一個進程管理員(process manager)庫來重啟你的應(yīng)用
當(dāng)你的操作系統(tǒng)崩潰時,使用它提供的初始化系統(tǒng)來重啟你的進程管理員。
Node.js應(yīng)用在遇到未處理異常時就會退出。你的首要任務(wù)是保證你的代碼的測試健全并且合理地處理了所有的異常。但是如有萬一,請準(zhǔn)備一個機制來確保它的自動重啟。
使用進程管理員(process manager)在開發(fā)環(huán)境下,你可以簡單地使用node server.js這樣的命令來啟動你的應(yīng)用。當(dāng)時在生產(chǎn)環(huán)境下這么做將是不被允許的。如果應(yīng)用崩潰了,在你手動重啟它之前,它都會處于離線狀態(tài)。為了保證你應(yīng)用的自動重啟,請使用一個進程管理員,它可以幫助你管理正在運行的應(yīng)用。
除了保證你的應(yīng)用的自動重啟,一個進程管理員還可以使你:
獲取當(dāng)前運行環(huán)境的性能表現(xiàn)和資源消耗情況。
自動地修改環(huán)境設(shè)置
管理集群(StrongLoop PM和pm2)
Node.js世界里比較流行的進程管理員有:
StrongLoop Process Manager
PM2
Forever
更多的它們之間的比較,你可以參閱這里。關(guān)于它們?nèi)叩暮喗?,你可以參閱這篇文章。
使用一個初始化系統(tǒng)接下來要保證的就是,在你的服務(wù)器重啟時,你的應(yīng)用也會相應(yīng)的重啟。盡管我們認為我們的服務(wù)器是十分穩(wěn)定的,但它們?nèi)杂袙斓舻目赡堋K詾榱吮WC在你的服務(wù)器時重啟時你的應(yīng)用也會重啟,請使用你操作系統(tǒng)內(nèi)建的初始化系統(tǒng)。如今比較主流的是systemd和Upstart。
以下是通過你的Express應(yīng)用來使用初始化系統(tǒng)的兩種方法:
將你的應(yīng)用運行于一個進程管理員中,然后將進程管理員設(shè)置為系統(tǒng)的一個服務(wù)。這個是比較推薦的做法。
直接通過初始化系統(tǒng)運行你的應(yīng)用。這個方法更為簡單,但你卻享受不到進程管理員帶來的福利。
SystemdSystems是一個linux系統(tǒng)的服務(wù)管理員。大多數(shù)的linux發(fā)行版都將它作為默認的初始化系統(tǒng)。
一個systems服務(wù)的配置文件也被稱為一個單元文件,有一個.service后綴。以下是一個直接管理Node.js應(yīng)用的例子:
[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
更多關(guān)于systemd的信息,請參閱這里。
UpstartUpstart是一個大多數(shù)linux發(fā)行版都可用的系統(tǒng)工具,用于在系統(tǒng)啟動時啟動任務(wù)和服務(wù),在系統(tǒng)關(guān)閉時停止它們,并且監(jiān)控它們。你可以先將你的Express應(yīng)用或進程管理員配置為一個服務(wù),然后Upstart會自動地在系統(tǒng)重啟后重啟它們。
一個Upstart服務(wù)被定義在一個任務(wù)配置文件中,有一個.conf后綴。下面的例子展示了如何創(chuàng)建一個名為“myapp”的任務(wù),且應(yīng)用的入口是/projects/myapp/index.js。
在/etc/init/下創(chuàng)建一個名為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。
除了自動重啟你的應(yīng)用,Upstart還為你提供了以下命令:
start myapp – 手動啟動應(yīng)用
restart myapp – 手動重啟應(yīng)用
stop myapp – 手動退出應(yīng)用
更多關(guān)于Upstart的信息,請參閱這里。
使用集群模式運行你的應(yīng)用在多核的系統(tǒng)里,你可以通過啟動一個進程集群來成倍了提升你應(yīng)用的性能。一個集群運行了你的應(yīng)用的多個實例,理想情況下,一個CPU核對應(yīng)一個實例。這樣,便可以在多個實例件進行負載均衡。
值得注意的是,由于應(yīng)用實例跑在不同的進程里,所以它們并不分享同一塊內(nèi)存空間。因為,應(yīng)用里的所有對象都是本地的,你不可以在應(yīng)用代碼里維護狀態(tài)。不過,你可以使用如redis這樣的內(nèi)存數(shù)據(jù)庫來存儲session這樣的數(shù)據(jù)和狀態(tài)。
在集群中,一個工作進程的崩潰不會影響到其他的工作進程。所以除了性能因素之外,多帶帶工作進程崩潰的相互不影響也是另一個使用集群的好處。一個工作進程崩潰后,請確保記錄下日志,然后重新通過cluster.fork()創(chuàng)建一個新的工作進程。
使用Node.js的cluster模塊Node.js提供了cluster模塊來支持集群。它使得一個主進程可以創(chuàng)建出多個工作進程。但是,比起直接使用這個模塊,許多的庫已經(jīng)為你封裝了它,并提供了更多自動化的功能:如node-pm或cluser-service。
緩存請求結(jié)果另一個提升你應(yīng)用性能的途徑是緩存請求的結(jié)果,這樣一來,對于同一個請求,你的應(yīng)用就不必做多余的重復(fù)動作。
使用一個如Varnish或Nginx這樣的緩存服務(wù)器可以極大地提升你應(yīng)用的響應(yīng)速度。
使用負載均衡不論一個應(yīng)用優(yōu)化地多么好,一個多帶帶的實例總是有它的負載上限的。一個很好的解決辦法就是將你的應(yīng)用跑上多個實例,然后在它們之前加上一個負載均衡器。
一個負載均衡器通常是一個反向代理,它接受負載,并將其均勻得分配給各個實例或服務(wù)器。你可以通過Nginx或HAProxy十分方便地架設(shè)一個負載均衡器。
使用了負載均衡后,你可以保證每個請求都根據(jù)它的來源被設(shè)置了獨特session id。當(dāng)然,你也可以使用如Redis這樣的內(nèi)存數(shù)據(jù)庫來存儲session。更多詳情,可以參閱這里。
負載均衡是一個相當(dāng)復(fù)雜的話題,更加細致的討論已超過了本文的范疇。
使用反向代理一個反向代理被設(shè)置與web應(yīng)用之前,用于支持各類對于請求的操作,如將請求發(fā)送給應(yīng)用,自動處理錯誤頁,壓縮,緩存,提供靜態(tài)文件,負載均衡,等等。
在生產(chǎn)環(huán)境中,這里推薦將Express應(yīng)用跑在Nginx或HAProxy之后。
最后原文鏈接:https://strongloop.com/strongblog/best-practices-for-express-in-production-part-two-performance-and-reliability/
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/86261.html
摘要:前言這將是一個分為兩部分,內(nèi)容是關(guān)于在生產(chǎn)環(huán)境下,跑應(yīng)用的最佳實踐。潛在的攻擊者可以通過它們進行針對性的攻擊。 前言 這將是一個分為兩部分,內(nèi)容是關(guān)于在生產(chǎn)環(huán)境下,跑Express應(yīng)用的最佳實踐。第一部分會關(guān)注安全性,第二部分最會關(guān)注性能和可靠性。當(dāng)你讀這篇文章時,假設(shè)你已經(jīng)對Node.js和web開發(fā)有所了解,并且對生產(chǎn)環(huán)境有了概念。 概覽 生產(chǎn)環(huán)境,指的是軟件生命循環(huán)中的某個階段。...
摘要:本文適合的讀者現(xiàn)在在手淘,京東,今日頭條,美柚等過億用戶的手機中的,都常見網(wǎng)頁,他們有更新快,靈活,便于分享和傳播的特性。這里有他們中的幾個的例子手淘,美柚。 本文適合的讀者??????? 現(xiàn)在在手淘,京東,今日頭條,美柚等過億用戶的手機app中的,都常見h5網(wǎng)頁,他們有更新快,靈活,便于分享和傳播的特性。這里有他們中的幾個h5的例子:(手淘,美柚)。這些app中都嵌者數(shù)以百計,千計的...
摘要:本文適合的讀者現(xiàn)在在手淘,京東,今日頭條,美柚等過億用戶的手機中的,都常見網(wǎng)頁,他們有更新快,靈活,便于分享和傳播的特性。這里有他們中的幾個的例子手淘,美柚。 本文適合的讀者??????? 現(xiàn)在在手淘,京東,今日頭條,美柚等過億用戶的手機app中的,都常見h5網(wǎng)頁,他們有更新快,靈活,便于分享和傳播的特性。這里有他們中的幾個h5的例子:(手淘,美柚)。這些app中都嵌者數(shù)以百計,千計的...
摘要:本文適合的讀者現(xiàn)在在手淘,京東,今日頭條,美柚等過億用戶的手機中的,都常見網(wǎng)頁,他們有更新快,靈活,便于分享和傳播的特性。這里有他們中的幾個的例子手淘,美柚。 本文適合的讀者??????? 現(xiàn)在在手淘,京東,今日頭條,美柚等過億用戶的手機app中的,都常見h5網(wǎng)頁,他們有更新快,靈活,便于分享和傳播的特性。這里有他們中的幾個h5的例子:(手淘,美柚)。這些app中都嵌者數(shù)以百計,千計的...
閱讀 2849·2021-08-20 09:37
閱讀 1607·2019-08-30 12:47
閱讀 1090·2019-08-29 13:27
閱讀 1685·2019-08-28 18:02
閱讀 749·2019-08-23 18:15
閱讀 3084·2019-08-23 16:51
閱讀 931·2019-08-23 14:13
閱讀 2125·2019-08-23 13:05