上一篇專欄簡單介紹了Connect模塊的基本架構,它的執(zhí)行模型十分簡單,中間件機制也使得它十分易于擴展,具備良好的可伸縮性。在Connect的良好機制下,我們本章開始將逐步解開Connect生態(tài)圈中中間件部分,這部分給予Connect良好的功能擴展。
也許你還記得我曾經(jīng)寫過的Node.js靜態(tài)文件服務器實戰(zhàn),那篇文章中我敘述了如何利用Node.js實現(xiàn)一個靜態(tài)文件服務器的許多技術細節(jié),包括路由實現(xiàn),MIME,緩存控制,傳輸壓縮,安全、歡迎頁、斷點續(xù)傳等。但是這里我們不需要去親自處理細節(jié),Connect
的static
中間件為我們提供上述所有功能。代碼只需寥寥3行即可:
var connect = require('connect');
var app = connect();
app.use(connect.static(__dirname + '/public'));
在項目中需要臨時搭建靜態(tài)服務器,也無需安裝apache之類的服務器,通過NPM安裝Connect之后,三行代碼即可解決需求。
這里需要提及的是在使用該模塊的一點性能相關的細節(jié)。
前一章提及,app.use()
方法在沒有指定路由信息時,相當于app.use("/", middleware)
。這意味著靜態(tài)文件中間件將會在處理所有路徑的請求。在動靜態(tài)請求混雜的場景下,靜態(tài)中間件會在動態(tài)請求時也調用fs.stat
來檢測文件系統(tǒng)是否存在靜態(tài)文件。這造成了不必要的系統(tǒng)調用,使得性能降低。
解決影響性能的方法既是動靜分離。利用路由檢測,避免不必要的系統(tǒng)調用,可以有效降低對動態(tài)請求的性能影響。
app.use('/public', connect.static(__dirname + '/public'));
在大型的應用中,動靜分離通常無需到一個Node.js實例中進行,CDN的方式直接在域名上將請求分離。小型應用中,適當?shù)倪M行動靜分離即可避免不必要的性能損耗。
緩存策略包含客戶端和服務端兩個部分。
客戶端的緩存,主要是利用瀏覽器對HTTP協(xié)議響應頭中cache-control
和expires
字段的支持。瀏覽器在得到明確的相應頭后,會將文件緩存在本地,依據(jù)cache-control
和expires
的值進行相應的過期策略。這使得重復訪問的過程中,瀏覽器可以從本地緩存中讀取文件,而無需從網(wǎng)絡讀取文件,提升加載速度,也可以降低對服務器的壓力。
默認情況下靜態(tài)中間件的最大緩存時設置為0,意味著它在瀏覽器關閉后就被清除。這顯然不是我們所期望的結果。除非是在開發(fā)環(huán)境可以無視maxAge
的設置外,生產(chǎn)環(huán)境請務必設置緩存,因為它能有效節(jié)省網(wǎng)絡帶寬。
app.use('/public', connect.static(__dirname + '/public', {maxAge: 86400000}));
maxAge
選項的單位為毫秒。YUI3的CDN服務器設置過期時間為10年,是一個值得參考的值。
靜態(tài)文件如果在客戶端被緩存,在需要清除緩存的時候,又該如何清除呢?這里的實現(xiàn)方法較多,一種較為推薦的做法是為文件進行md5處理。
http://some.url/some.js?md5
當文件內容產(chǎn)生改變時,md5值也將發(fā)生改變,瀏覽器根據(jù)URL的不同會重新獲取靜態(tài)文件。md5的方式可以避免不必要的緩存清除,也能精確清除緩存。
由于瀏覽器本身緩存容量的限制,盡管我們可能設置了10年的過期時間,但是也許兩天之后就被新的靜態(tài)文件擠出了本地緩存。這將持續(xù)引起靜態(tài)服務器的響應,也即意味著,客戶端緩存并不能完全解決降低服務器壓力的問題。
為了解決靜態(tài)服務器重復讀取磁盤造成的壓力,這里需要引出第二個相關的中間件:staticCache
。
app.use(connect.staticCache());
app.use(“/public”, connect.static(__dirname + '/public', {maxAge: 86400000}));
這是一個提供上層緩存功能的中間件,能夠將磁盤中的文件加載到內存中,以提高響應速度和提高性能。
它的官方測試數(shù)據(jù)如下:
static(): 2700 rps
node-static: 5300 rps
static() + staticCache(): 7500 rps
另一個專門用于靜態(tài)文件托管的模塊叫node-static
,其性能是Connect靜態(tài)文件中間件的效率的兩倍。但是在緩存中間件的協(xié)助下,可以彌補性能損失。
事實上,這個中間件在生產(chǎn)環(huán)境下并不推薦被使用,而且它將在Connect 3.0版本中被移除。但是它的實現(xiàn)中有值得玩味的地方,這有助于我們認識Node.js模型的優(yōu)缺點。staticCache
中間件有兩個主要的選項:maxObjects
和maxLength
。代表的是能存儲多少個文件和單個文件的最大尺寸,其默認值為128和256kb。為何會有這兩個選項的設定,原因在于V8有內存限制的原因,作為緩存,如果沒有良好的過期策略,緩存將會無限增加,直到內存溢出。設置存儲數(shù)量和單個文件大小后,可以有效抑制緩存區(qū)的大小。
事實上,該緩存還存在的缺陷是單機情況下,通常為了有效利用CPU,Node.js實例并不只有一個,多個實例進程之間將會存在冗余的緩存占用,這對于內存使用而言是浪費的。
除此之外,V8的垃圾回收機制是暫停JavaScript線程執(zhí)行,通過掃描的方式?jīng)Q定是否回收對象。如果緩存對象過大,鍵太多,則掃描的時間會增加,會引起JavaScript響應業(yè)務邏輯的速度變慢。
但是這個模塊并非沒有存在的意義,上述提及的缺陷大多都是V8內存限制和Node.js單線程的原因。解決該問題的方式則變得明了。
風險轉移是Node.js中常用于解決資源不足問題的方式,尤其是內存方面的問題。將緩存點,從Node.js實例進程中轉移到第三方成熟的緩存中去即可。這可以保證:
Connect推薦服務器端緩存采用varnish
這樣的成熟緩存代理。而筆者目前的項目則是通過Redis
來完成后端緩存的任務。
更多建議: