NodeJS那些事

下半年作了挺多活動型需求,由於咱們業務人力有限,我在業務的策略是不依賴NodeJS。javascript

而這些活動型需求,是能夠用NodeJS來練練手。html

ExpressJS

一個Web服務框架,幾經轉手,如今應該是IBM旗下的產品了。java

之前咱們用PHP來開發Web服務,語言層面屏蔽了不少HTTP協議的東西,能夠專心業務邏輯。node

而NodeJS不一樣,自己就跑Web服務(無論前面是否加個Nginx反向代理),因此挺多HTTP協議的細節須要咱們深刻了解的。ajax

ExpressJS經過大量中間件,來幫咱們屏蔽掉這些HTTP協議的細節,例如body-parser和cookie-parser,幫咱們解析HTTP Body和Cookies部分的內容。編程

app.use([path],function(req, res, next){})

整個ExpressJS,最重要的部分就是app.use()。不管是中間件?仍是咱們常見的app.get()\app.post(),都是從app.use()衍生出來。json

每個請求到達ExpressJS後,其處理流程是按順序進入各個app.use()傳入的回調函數中。api

  • 若是該app.use()帶有path參數,則匹配path參數纔會執行該回調函數。服務器

  • 若是該app.use()的回調函數最後還調用了next方法,則此次請求的處理流程會繼續流向下一個app.use()。cookie

正由於如此,每一個回調函數,只有一次調用的機會,你要麼用來處理req階段,要麼用來處理res階段。(宣稱是下一代Web框架的Koa,則是利用ES6裏面的語法糖,實現了一個回調函數有屢次執行的機會)。

難道ExpressJS就不能讓回調函數既處理req又處理res嗎?

非也,app.get()\app.post()就能同時處理req和res,只是ExpressJS的把能同時處理req和res的稱爲路由(Routing),而只能處理其中一種的稱爲中間件(Middleware)。這樣造成一個不成文的約定,用中間件來加工req,用路由來加工req和res。

其實中間件一次只能處理一個階段是有好處的。HTTP有一個特性,是HTTP Header必須早於HTTP Body返回。若是中間件也用來處理res,就會有很是大概率出現res.send()早於res.header()而致使的故障。日常咱們的精力關注在路由上,中間件觸發的故障會比較難發現和定位。

winston

在ExpressJS官網的最佳實踐裏有提到日誌這點,平時咱們用的console.log()是一個同步的語法,開發階段問題不大,但不適合生產環節,官方推薦winston和Bunyan兩個庫,我這裏用winston。

winston支持分級日誌,自帶info\warn\error三級

var winston = require('winston');
winston.info("127.0.0.1 - there's no place like home");
winston.warn("127.0.0.1 - there's no place like home");
winston.error("127.0.0.1 - there's no place like home");

還能夠傳遞自定義等級

winston.log('level','log info');
winston.log('level','log info');

咱們能夠在ExpressJS的最後一個app.use()裏面,作一個兜底的異常處理回調。

app.use(function(err, req, res){
    winston.error('unKnown Error, req: '+ req.originUrl, err);
    res.status(500);
});

app.use(function(err, req, res){
    winston.error('unKnown Error, req: '+ req.originUrl, err);
    res.status(500);
});

這樣就把咱們沒有預計到的異常,也兜底接住了,並記錄在日誌中便於回溯。

winston還有一個 winston.profile('name') 的方法,用來記錄兩個點的時間間隔,能夠作性能統計埋點。

Request & Promise

NPM依賴榜排行第七的庫(不知道是第七仍是第三),跑NodeJS服務常常能用到,用來調用第三方接口。

Request的具體用法和$.ajax()雷同。

因爲Request是異步的,爲了便於業務使用,最好用Promise對每一個具體的API調用進行封裝。NodeJS和IO.js合併後,已經完美支持Promise語法了。

Promise的語法這裏不展開,直接說怎麼封裝Request。

function getStaffInfoByName (name){

    // 我習慣將外部接口都歸集到一個api對象中便於查看
    var getStaffInfoByName = api.getStaffInfoByName;

    // 這個外部接口,是經過GET方式傳參數的,最近學會一種{}佔位替換的寫法,讓接口更易讀
    var url = getStaffInfoByName.replace('{name}', name);

    // 返回外部一個Promise對象
    return new Promise(function(resolve, reject){

        // 其實接口的參數也能夠這裏配置,看我的習慣
        var reqOpts = {
            url: url
        };

        // 發起調用
        request(reqOpts, function (error, response, body) {
            if (!error) {

                var json = JSON.parse(body);

                if (json.Ret === 0){

                    // 若是一切正常,就resolve,並傳遞數據
                    resolve(json.Data);

                } else {

                    // 業務異常就reject,並傳遞錯誤信息
                    reject(json.ErrMsg);

                }

            } else {

                // 這裏reject網絡錯誤
                reject(error);

            }
        });

    });

}

// 把這個方法暴露到外部
module.exports = getStaffInfoByName;

業務調用的時候,就能夠安心處理正常邏輯,異常已經被屏蔽了

app.get('/getUserInfo', function(req, res, next){

    var name = req.query.name;

    getStaffInfoByName(name).then(function(data){
        res.render('user', data);
    }).catch(next);

});

你看,如今的業務邏輯就是渲染。而其餘異常,則會被一路拋出,直到最後一個app.use()來作兜底異常處理。

child_process & PhantomJS

這裏我要先先說說,NodeJS的到來,讓我竟然有機會學習進程\線程的編程。

這裏貼一個ChildProces的官方栗子。

var child_process = require('child_process');

// spawn的第一個參數是執行的命令,第二個則是命令的參數列表,返回值的該進程的句柄
var ls = child_process.spawn('ls', ['-lh', '/usr']);

// 支持對進程stdout的監聽
ls.stdout.on('data', function (data) {
  console.log('stdout: ' + data);
});

// 支持對進程stderr的監聽
ls.stderr.on('data', function (data) {
  console.log('stderr: ' + data);
});

// 支持監聽該進程的close事件
ls.on('close', function (code) {
  console.log('child process exited with code ' + code);
});

說完語法,說應用。

咱們業務有兩個地方用到了PhantomJS,PhantomJS支持CLI調用和Web服務兩種方式,而其自身的Web服務是經過Mongoose實現的。

最初咱們在使用PhantomJS的Web服務的時候,常常遇到其假死的情況。因爲是假死,各類守護進程的策略無法實施(偵測不到進程的任何異常),最後同事採用暴力的定時kill後重啓策略。

也許上面的不穩定,是咱們PhantomJS的腳本寫的有問題致使的,但無論怎麼樣,腳本問題致使服務不穩定是不能接受的,後來咱們改用CLI的方式調用PhantomJS。

app.get('/thumbnail', function(req, res, next){

    // 經過GET ?target={url}的方式,傳遞截圖目標網址
    var url = req.query.target;

    var child_process = require('child_process');

    // 以子進程的方式啓動PhantomJS
    var phantom = child_process.spawn('phantomjs', ['thumbnail.js', url]);

    // 監聽PhantomJS進程的exit事件
    phantom.on('exit', function(code){

        switch (code){
            case 0:
                res.send('截圖成功')
                break;
            case 1:
                res.send('截圖失敗,緣由是xxx');
                break;
            default:
                res.send('截圖失敗,緣由未知');
                break;
        }

    });

});

這裏咱們用ExpressJS替換了PhantomJS自帶的Mongoose來實現Web服務,並每次經過子進程的方式喚起PhantomJS,而咱們自己的ExpressJS由PM2來保障執行,這樣就能完全解決因爲腳本質量致使的服務假死問題。

PM2

一個給NodeJS服務用的守護進程,支持API和靜態配置,很是強大,我建議大部分用NodeJS跑持久化服務的地方都用PM2。

PM2有多種啓動方式,一般狀況,建議將PM2的啓動配置靜態化到一個pm2.json文件中,而後經過 pm2 start pm2.json 來啓動。

PM2支持將log重定向,這對於多硬盤\多分區的服務器是很是友好的,咱們服務器的根目錄容量就很是小,如稍加註意,就會被這些NodeJS的log給撐爆,須要重定向到大容量目錄下。

同步於個人博客

相關文章
相關標籤/搜索