Node.js APM實(shí)現(xiàn)分析

APM(Application Performance Management),中文名稱應(yīng)用性能管理。百度百科上給了比較系統(tǒng)的介紹,有興趣的同學(xué)可以查看:應(yīng)用性能管理;知乎上也有對(duì)這個(gè)方向的簡單探討,鏈接:APM(應(yīng)用性能管理)在中國前景如何?。
本文從聽云的Nodejs監(jiān)控源碼入手,探索其實(shí)現(xiàn)的的一些原理和細(xì)節(jié)。源碼可以在聽云官網(wǎng)上找到。

整體結(jié)構(gòu)

從下圖可以看到整個(gè)源碼結(jié)構(gòu)。metrics主要處理性能指標(biāo),負(fù)責(zé)把收集到的指標(biāo)轉(zhuǎn)成服務(wù)器識(shí)別的格式;serve負(fù)責(zé)服務(wù)器的連接建立、數(shù)據(jù)傳送等和服務(wù)器交互的邏輯;options是處理配置信息,如服務(wù)器的地址、用戶的key等;util是方法集合,如日志處理等,它的功能比較復(fù)雜;parsers是程序的核心,對(duì)用戶使用到的框架或者API進(jìn)行封裝處理。


程序框架.png
啟動(dòng)

index.js中給出來程序的啟動(dòng)流程:

var init = function init() {
    ...
    // 1. 初始化配置信息
    var config = require('./options/config.js').init();
    var Agent = require('./agent.js');

    // 2. 根據(jù)配置信息生成agent對(duì)象 agent對(duì)象會(huì)攜帶性能相關(guān)的信息
    agent = new Agent(config);

    // 3. shimmer提供函數(shù)的封裝模塊
    var shimmer = require('./util/shimmer.js');
    // 對(duì)Node中的加載做一層封裝
    shimmer.patchModule(agent);
    // 對(duì)http進(jìn)行封裝
    shimmer.bootstrapInstrumentation(agent);

    // 收集應(yīng)用基本信息 如物理內(nèi)存使用情況、事件隊(duì)列排隊(duì)
    // 啟動(dòng)定時(shí)器  50s間隔發(fā)送運(yùn)行參數(shù)
    return agent.start();
}
var start_message = init();
運(yùn)行

啟動(dòng)流程中,shimmer. patchModule會(huì)對(duì)Node模塊加載的_load方法進(jìn)行了處理:

patchModule : function patchModule(agent) {
    logger.debug("Wrapping module loader.");
    var Module = require('module');

    shimmer.wrapMethod(Module, 'Module', '_load', function cb_wrapMethod(load) {
      return function cls_wrapMethod(file) {
        return _postLoad(agent, load.apply(this, arguments), file);
      };
    });
  }

Node在模塊加載時(shí)都會(huì)調(diào)用到Module的_load方法。當(dāng)require一個(gè)模塊時(shí),程序會(huì)根據(jù)模塊的名字決定加載執(zhí)行哪一個(gè)封裝邏輯;如果沒有封裝邏輯,那么直接執(zhí)行原模塊:

function _postLoad(agent, nodule, name) {
    var base = path.basename(name);
    // 原生express的base為express,封裝的parsers/wrappers/express.js,其base為express.js。 依據(jù)此避免了循環(huán)依賴
    var wrapper_module = (name === 'pg.js') ? 'pg': base;
    //  WRAPPERS是由express、redis、mysql等模塊名組成的數(shù)組
    if (WRAPPERS.indexOf(wrapper_module) !== -1) {
        logger.debug('wrap %s.', base);
        var filename = path.join(__dirname, '../parsers/wrappers', wrapper_module + '.js');
        instrument(agent, base, filename, nodule);
    }
    if (FUN_WRAPPERS.indexOf(wrapper_module) !== -1) {
        logger.debug('wrap %s.', base);
        var filename = path.join(__dirname, '../parsers/wrappers', wrapper_module + '.js');
        return retInstrument(agent, base, filename, nodule);
    }
    return nodule;
}

instrument函數(shù)的邏輯就是把封裝模塊加載執(zhí)行:

function instrument(agent, shortName, fileName, nodule, param) {
  try {
    require(fileName)(agent, nodule, param);
  }
  catch (error) {
    logger.verbose(error, "wrap module %s failed.",  path.basename(shortName, ".js"));
  }
}

Express探針

其對(duì)Express的封裝邏輯基本包括以下幾個(gè)步驟:1. 判斷應(yīng)用程序中的Express版本; 2. 依據(jù)版本執(zhí)行封裝邏輯。 以Express4.0為例,程序?qū)ο旅鎺讉€(gè)方法做了封裝處理:

shimmer.wrapMethodOnce(express.application, 'express.application', 'init', app_init);
shimmer.wrapMethodOnce(express.response, 'express.response', 'render', wrapRender.bind(null, 4));
shimmer.wrapMethodOnce(express.Router, 'express.Router', 'process_params', wrapProcessParams.bind(null, 4));
shimmer.wrapMethodOnce(express.Router, 'express.Router', 'use', wrapMiddlewareStack.bind(null, 'use'));
shimmer.wrapMethodOnce(express.Router, 'express.Router', 'route', wrapMiddlewareStack.bind(null, 'route'));

wrapMethodOnce的邏輯如下,主要是對(duì)原有的方法做一層封裝:

wrapMethodOnce : function wrapMethodOnce(nodule, noduleName, method, wrapper ) {
    if (!noduleName) noduleName = '[unknown]';
    var method_name = noduleName + '.' + method;
    var original = nodule[method];
    if (!original) {
      return logger.debug("%s not defined, skip wrapping.", method_name);
    }
    if ( original.__TY_unwrap ) return;
    var wrapped = wrapper(original);
    wrapped.__TY_original = original;
    wrapped.__TY_unwrap = function __TY_unwrap() {
      nodule[method] = original;
      logger.debug("Removed instrumentation from %s.", method_name);
    };

    nodule[method] = wrapped;
    if (shimmer.debug) instrumented.push(wrapped);
  }

以wrapRender為例,它在原來的方法上的基礎(chǔ)上了增加了指標(biāo)收集的邏輯:

function wrapRender(version, render) {
        return function wp_Render(view, options, cb, parent, sub) {
            if ( ! agent.config.enabled ) return render.apply(this, arguments);
            if (!tracer.getAction()) return render.apply(this, arguments);
            var classname = (version < 3)?'http.ServerResponse':'express.response';
//            var name = "Express/" + view.replace(/\//g, "%2F") + '/' + classname + '.render';
            var name = "Express/" + classname + '/render';
            var segment_info = {
                metric_name : name,
                call_url:"",
                call_count:1,
                class_name: classname,
                method_name: "render",
                params : {}
            }
            var segment = tracer.addSegment(segment_info, record);
            if ( typeof options === 'function' ) {
                cb = options;
                options = null;
            }
            var self = this;
            var wrapped = tracer.callbackProxy(function render_cb(err, rendered){
                segment.end();
                if ( typeof cb === 'function' ) return cb.apply(this, arguments);
                if (err) {
                    logger.debug(err, "Express%d %s Render failed @action %s:", version, name, segment.trace.action.id);
                    return self.req.next(err);
                }
                var returned = self.send(rendered);
                logger.debug("Express%d %s Rendered @action %s.", version, name, segment.trace.action.id);
                return returned;
            });
            return render.call(this, view, options, wrapped, parent, sub);
        };
    }

因?yàn)楹驼?qǐng)求有關(guān),上述邏輯執(zhí)行完會(huì)進(jìn)入到http的封裝邏輯中。當(dāng)一個(gè)請(qǐng)求發(fā)送后,會(huì)進(jìn)入到http.ServerResponse.prototype的end方法中:

shimmer.wrapMethod(response, 'http.ServerResponse.prototype', 'end', function wrspe(end) {
            return wrapEnd(agent, end, action);
        });

wrapWrite = wrapEnd = function(agent, original, action) {
    return function(data, encoding, callback) {
        ...

        // 性能跟蹤
        if (!action.head_writed) {
            setTraceData(action, this)
        }
        if (this.statusCode != 200) {
            logger.debug('statusCode is %s, skip injecting code.', this.statusCode);
            return original.call(this, resultData || data, encoding, callback);
        }

        if (data && _tingyun.needInject(agent, this._headers || this._header) && !_tingyun.injected) {
            // 如果需要對(duì)前端模塊嵌入代碼 執(zhí)行...
        }
        
        return original.call(this, resultData || data, encoding, callback);
    }
};

指標(biāo)收集

特定時(shí)間間隔發(fā)送一次性能指標(biāo),默認(rèn)為50s

// 啟動(dòng)定時(shí)器
Agent.prototype._startTimer = function _startTimer(interval) {
    var agent = this;
    this.hTimer = setInterval(function () { agent._on_timer(); }, interval * 1000);
    if (this.hTimer.unref) this.hTimer.unref();
};

// 發(fā)送指標(biāo)
Agent.prototype._on_timer = function _on_timer() {
    ...
    // 發(fā)送時(shí) 先收集 然后傳給服務(wù)器
    this._send_metrics(on_upload_ret);
};

具體的指標(biāo)收集是個(gè)比較復(fù)雜的邏輯,類型分為action、sql等;和性能有關(guān)的類包括: trace、action、segment等。具體的分析后續(xù)加上。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,291評(píng)論 25 708
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,680評(píng)論 19 139
  • 11月27日,發(fā)現(xiàn)寶寶臉上長了紅色的小疹子,我一直以為是濕疹,整個(gè)額頭和臉頰處都是。先給他涂尿布疹的藥膏Desit...
    Lucas的未來閱讀 4,506評(píng)論 0 1
  • 2027年,我沒有記錯(cuò)。 但我怎么也不會(huì)想到,現(xiàn)在的我,能透過十年的光陰,如此清晰的看到2017年的你,我自己! ...
    寶貝的天空閱讀 250評(píng)論 0 9
  • 行尸走肉是一直在追的美劇,看著一幫人在亂世中與行尸和惡勢(shì)力的反抗,為了能夠生存,即便在這樣一個(gè)世界,也極度渴望活著...
    思齊_yang閱讀 249評(píng)論 5 3

友情鏈接更多精彩內(nèi)容