Express 做爲 Node.js 的框架,現在發展可謂如日中天。我很喜歡其靈活、易擴展的設計理念。尤爲是該框架的中間件架構設計:使得在應用中加入新特性更加標準化、成本最小化。這篇文章,我會嘗試編寫一個很是簡單、小巧的中間件,完成服務端緩存功能,進而優化性能。前端
說到中間件,Express 官網對它的闡述是這樣的:git
「Express 是一個自身功能極簡,徹底是路由和中間件構成一個web開發框架:從本質上來講,一個 Express 應用就是在調用各類中間件。」github
也許你使用過各類各樣的中間件進行開發,可是可能並不理解中間件原理,也沒有深刻過 Express 源碼,探究其實現。這裏並不打算長篇大論幫您分析,可是使用層面上大體能夠參考下圖:web
建議有興趣、想深刻的讀者本身分析,有任何問題歡迎與我討論。即使您不打算深刻,也不會影響對下文中間件編寫的理解。redis
緩存已經被普遍應用,來提升頁面性能。一說到緩存,可能讀者腦海裏立刻冒出來:「客戶端緩存,CDN 緩存,服務器端緩存......」。另外一維度上,也會想到:「200(from cache),expire,eTag......」等概念。數據庫
固然做爲前端開發者,咱們必定要明白這些緩存概念,這些緩存理念是相對於某個具體用戶訪問來講的,性能優化體如今單個用戶上。好比說,我第一次打開頁面 A,耗時超長,下一次打開頁面因爲緩存的做用,時間縮短了。express
可是在服務器端,還存在另一個維度,思考一下這樣的場景:npm
咱們有一個靜態頁面 B,這個頁面服務端須要從數據庫獲取部分數據 b1,根據 b1 又要計算獲得部分數據 b2,還得作各類高複雜度操做,最終才能「東拼西湊」出須要返回的完整頁面 B,整個過程耗時2s。後端
那麼面臨的災難就是,user1 打開頁面耗時2s,user2一樣打開頁面耗時2s......而這些頁面都是靜態頁面 B,內容是徹底同樣的。爲了解決這個災難,這時候咱們也須要緩存,這種緩存就叫先作服務端緩存(server-side cache)。瀏覽器
總結一下,服務端緩存的目的其實就是對於同一個頁面請求,而返回(緩存的)一樣的頁面內容。這個過程徹底獨立於不一樣的用戶。
上面的話有些拗口,能夠參考英文表達更清晰:
The goal of server side cache is responding to the same content for the same request independently of the client’s request.
所以,下面展現的 demo 在第一次請求到達時,服務端耗費5秒來返回 HTML;而接下來再次請求該頁面,將會命中緩存,不過是哪一個用戶訪問,只須要幾毫秒即可獲得完整頁面。
其實上文提到的緩存概念很是簡單,稍微有些後端經驗的同窗都能很好理解。可是這篇文章除去科普基本概念外,更重要的就是介紹 Express 中間件思想,並本身來實現一個服務端緩存中間件。
讓咱們開工吧!
最終 Demo 代碼,歡迎訪問它的Github地址。
我將會使用 npm 上 memory-cache 這個包,以方便進行緩存的讀寫。最終的中間件代碼很簡單:
'use strict'
var mcache = require('memory-cache');
var cache = (duration) => {
return (req, res, next) => {
let key = '__express__' + req.originalUrl || req.url
let cachedBody = mcache.get(key)
if (cachedBody) {
res.send(cachedBody)
return
} else {
res.sendResponse = res.send
res.send = (body) => {
mcache.put(key, body, duration * 1000);
res.sendResponse(body)
}
next()
}
}
}複製代碼
爲了簡單,我使用了請求 URL 做爲 cache 的 key:
緩存的有效時間是10秒。
最終在判斷以外,咱們的中間件把控制權交給下一個中間件。
最終使用和測試以下代碼:
app.get('/', cache(10), (req, res) => {
setTimeout(() => {
res.render('index', { title: 'Hey', message: 'Hello there', date: new Date()})
}, 5000) //setTimeout was used to simulate a slow processing request
})複製代碼
我使用了 setTimeout 來模擬一個超長(5s)的操做。
打開瀏覽器控制面板,發如今10秒緩存到期之內:
至於爲何 cache 中間件要那樣子寫、next() 爲何是中間件把控制權傳遞,我並不打算展開去講。有興趣的讀者能夠看一下 Express 源碼。
仔細看咱們的頁面,再去體會一下實現代碼。也許細心的讀者能發現一個問題:剛纔的實現咱們緩存了整個頁面,並將 date: new Date() 傳入了 jade 模版 index.jade 裏。那麼,在命中緩存的條件下,10秒內,頁面沒法動態刷新來同步,直到10秒緩存到期。
同時,咱們何時可使用上述中間件,進行服務端緩存呢?固然是靜態內容纔可使用。同時,PUT, DELETE 和 POST 操做都不該該進行相似的緩存處理。
一樣,咱們使用了 npm 模塊:memory-cache,它存在優缺點以下:
若是這些弊端 really matter,在實際開發中咱們能夠選擇分佈式的 cache 服務,好比 Redis。一樣你能夠在 npm 上找到:express-redis-cache 模塊使用。
在真實的開發場景中,服務端緩存已經成爲 common sense,可是在 Node.js 的世界裏,體會其中間件思想,本身手動編寫服務,一樣樂趣無窮。
與實踐相結合,我認爲真正緩存整個頁面(如同 demo 那樣)並非一個推薦的作法(當時實際場景實際分析),一樣使用請求 url 做爲緩存的 key 也有待考慮。好比,頁面中的一些靜態內容可能會在其餘頁面中重複使用到,複用就成了問題。
真實場景下,一切設計和邏輯都要爲本身業務狀況所負責。脫離需求談實現,都是耍流氓。這個 demo 簡易輕巧,有須要的讀者能夠訪問它的Github地址,歡迎玩出各類花樣。
Happy Coding!