iT邦幫忙

DAY 10
9

node.js伺服器實戰系列 第 10

node.js伺服器實戰(10) - 加入cache機制

讀取靜態檔案會大幅影響伺服器速度,所以要試試看怎麼做file cache。
使用ab做簡單的效能測試

對於檔案系統對應的伺服器,如果每次response都需要讀取一次檔案,速度會很慢,所以加入一個簡單的cache機制來改善效能應該是一個不錯的做法。不過在這之前,先用ab來作效能測試,來得到一些可以比較的數據,才能確保實做cache是有效的。

先針對之前做的index.html做簡單的測試,比較一下讀取檔案造成的效能差距:

* 直接輸出index.html的內容:1824 req/sec
* 讀取後輸出index.html的內容:511 req/sec

看起來的確有影響,接下來就想一下怎麼做cache。

cache構想

利用Javascript的物件,其實就可以做出簡單的cache。

  1. 為了讓其它功能也可以使用cache,需要先註冊一個type來初始化
  2. 透過key來存取value
  3. 設計存取的界面

實作檔案系統cache

接下來就把cache機制實作出來吧(lib/cache.js):

 var cache = {};
 function regist(type) {
     if(!cache[type]) {
         cache[type] = {};
     }
 }
 function get(type, key) {
     if(cache[type] && cache[type][key]) {
         return cache[type][key];
     }
 }
 function put(type, key, val) {
     if(cache[type]) {
         cache[type][key] = val;
     }
 }
 function del(type, key) {
     if(cache[type] && cache[type][key]) {
         cache[type][key] = null;
         delete cache[type][key];
     }
 }
 function query(type, key) {
     if(cache[type] && cache[type][key]) {
         return true;
     } else {
         return false;
     }
 }
 module.exports = {
     regist: regist,
     get: get,
     put: put,
     del: del,
     query: query
 };

伺服器部份也需要做更動:

  1. 讀取檔案的機制已經有點太複雜,所以把它拉出來,放到tools.getRes()中,在伺服器中呼叫
  2. 處理結果及寫入http.ServerResponse,用一個callback函數來做,傳給tools.getRes()作為參數
  3. 讀取檔案系統之前,先去cache找是否有東西,有東西則使用cache中的資料,沒有的話才實際讀取檔案

調整過程式以後長這樣(lib/evolve.js):

 var http = require('http'),
     fs = require('fs'),
     url = require('url'),
     mime = require('../deps/mime'),
     cache = require('./cache');
     path = require('path');
 
 var tools = {
     getRes: function(respath, dirindex, cb) {
         if(cache.query('fscache', respath)) {
             cb(false, respath, cache.get('fscache', respath));
         } else {
             fs.stat(respath, function(err, stats) {
                 if(err) {
                     cb(true, respath);
                 } else {
                     if(stats.isDirectory()) {
                         var ic=0;
                         fs.readFile(path.join(respath, dirindex[ic]), function dih(err, data) {
                             var tmp = path.join(respath, dirindex[ic]);
                             ic++;
                             if(err) {
                                 if(ic<dirindex.length) {
                                     fs.readFile(path.join(respath, dirindex[ic]), dih);
                                 } else {
                                     cb(true, tmp);
                                 }
                             } else {
                                 var res = {"type": mime.lookup(tmp), "data": data};
                                 cb(false, tmp, res);
                                 cache.put('fscache', respath, res);
                             }
                         });
                     } else {
                         fs.readFile(respath, function(err, data) {
                             if(err) {
                                 cb(true);
                             } else {
                                 var res = {"type": mime.lookup(respath), "data": data};
                                 cb(false, respath, res);
                                 cache.put('fscache', respath, res);
                             }
                         });
                     }
                 }
             });
         }
     }
 };
 
 
 var evolve = function(conf) {
     cache.regist('fscache');
     var server = http.createServer(function(request, response) {
         var urlObj = url.parse(request.url);
         var respath = path.join(conf.basedir, urlObj.pathname);
         console.log('request: ' + respath);
         tools.getRes(respath, conf.dirindex, function(err, realpath, data) {
             if(err) {
                 console.log(realpath + ' not exists.');
                 response.writeHead(404, {"Content-Type":"text/html"});
                 response.end('<h1>404: Request resource not found.</h1>');
             } else {
                console.log(realpath + ' exists.');
                response.writeHead(200, {
                    "Content-Type": data.type,
                    "Content-Length": data.data.length
                });
                response.end(data.data);
             }
         });
     });
     this.listen = function(port, addr) {
         server.listen(port, addr);
         return this;
     };
 };
 module.exports = evolve;

跑一下ab,看看cache是否能改善效能:
* 加入cache機制後讀取index.html內容:1069 req/sec

雖然還是比hello world慢了不少,但是比沒有fs cache之前快了一倍。(在我的MBA上跑,到16000~18000次requests時會發生GC...速度就慢爆了XD,這個數值是只跑到10000次requests)

不過cache是一門大學問,這裡實作的cache機制,並不適合需要提供大量靜態檔案的場合。V8有一些記憶體限制,cache太大的話就會直接爆炸。如果要做好cache,那還需要更複雜的管理機制(例如設定cache容量限制,依照cache使用量來作加權,然後在超過使用量時清除用量低的cache資料等等)

相關文章


上一篇
node.js伺服器實戰(9) - 模組化
下一篇
node.js伺服器實戰(11) - 加入router機制
系列文
node.js伺服器實戰33

2 則留言

0
chiounan
iT邦研究生 1 級 ‧ 2011-10-21 11:03:26

讚很認真

0
fillano
iT邦超人 1 級 ‧ 2011-10-21 11:54:32

補充一下,在我的MBA上面跑ab -n20000 http://localhost:8443/,到第16377~16380次request就會停個幾秒鐘。我另外測了記憶體用量,看起來跟GC沒正相關。在windows上跑也沒出現同樣的問題...所以看起來就是Mac版本、作業系統環境這一類的問題,跟node.js不一定相關。

我要留言

立即登入留言