前言
筆者之前有使用輕量級的easy-monitor2.0
對項目進行內存泄漏的排查;
本文主要是對2.0版本的源碼學習和筆記整理。目的是為了個人的技術提升,想去了解一個nodejs監控的整體實現。如果哪里有理解不對的地方歡迎讀者指出。
本文底部會有原作者在cnode原文章的鏈接;
整體架構
這里引用一下原文中的架構圖
整體上分為了三個模塊:
業務進程中運行的embrace模塊
dashboard看板服務模塊
web頁面
下面從源碼層面對各模塊進行分析;
初始化
這個庫對外暴露的是一個方法。
'use strict';
const easyMonitor = require('easy-monitor');
easyMonitor('Mercury');
const express = require('express');
const app = express();
app.get('/hello', function (req, res, next) {
res.send('hello');
});
app.listen(8082);
入口文件index.js
引入根目錄的dispatch.js
這個文件主要做了以下工作:
- 對
src_logic/common
目錄的js文件進行初始化,導出一個對象;收斂了common
目錄下的所有模塊方法。并且遍歷該對象屬性值,如果包含initP
方法就進行調用;
//獲取基礎配置, pre 表示預先加載的文件,params 表示對應的參數
const common = _common({ pre: ['config', 'logger', 'utils', 'cache'], param: { config: options } });
yield common.utils.commonInitP(common);
-
src_logic/common
目錄下的每一個模塊暴露的都是一個初始化的方法,接受的參數是一致的,其實對應的就是common
目錄被前置加載的模塊;
function (common, config, logger, utils, cache) { ... }
在初始化上述
config
模塊時,使用了類似的初始化過程對src_logic/config
目錄下的所有配置文件進行了初始化;并導出了一個對象,收斂了所有配置選項;
詳細過程閱讀src_logic/common/common.config.js
模塊初始話完畢后便是運行
embrace
模塊的start
方法
通過fork子進程形式運行dashboard
模塊
(沒有分析集群部署模式)
//非 cluster 模式下,embrace 嵌入業務進程,dashboard 以 fork 形式啟動
embrace.start(config, common);
common.utils.forkNode(path.join(rootPath, 'dashboard/_fork.js'), [JSON.stringify(options)]);
關于RPC
這里插入一下RPC
的概念,方便理解下面的embrace
與dashboard
之間的通信
這里引用一段網絡上的解釋:原文
RPC (Remote Procedure Call:遠程過程調用):一種進程間通信方式。允許像調用本地服務一樣調用遠程服務
RPC架構:
包含四個核心組件
- 客戶端(client):服務的調用方
- 服務端(server):服務提供方
- 客戶端存根(client stub):將客戶端請求參數打包成網絡消息,再發給服務方
- 服務端存根(server stub):接收客戶端發來的消息,將消息解包,并調用本地方法
其實源碼中embrace
模塊對應的就是Client,dashboard
模塊對應的就是Server,基于TCP鏈接實現的通信;
embrace模塊
embrace
模塊暴露出的start
方法將上文中初始化后完整的config
對象和common
對象作為了參數傳入;
這里主要進行的工作是:
- 加載
embrace/dispatch
模塊 - 加載所有
embrace/controller
目錄下的邏輯處理方法 ,集成到一個controller
對象上
//獲取 embrace 的 dispatch 信息
const dispatch = _require('embrace/dispatch');
const controller = dispatch.controller(config, common, dbl);
- 啟動
tcp
客戶端服務;(注:與dashboard
模塊通信的客戶端)
并設置this指向{ controller }
//啟動 tcp 客戶端
const tcpClient = dispatch.tcp;
tcpClient.apply({ controller }, [config, common, dbl]);
embrace/dispatch模塊
上文中引入的embrace/dispatch
模塊對外暴露了上文中用到的兩個方法
-
createTcpClient
:啟動tcp
客戶端服務
這里使用了net.Socket類創建了socket
實例,并且調用了實例方法connect
,以tcp模式進行鏈接。
//和服務器建立鏈接
const client = new net.Socket();
client.connect(config.embrace.tcp_port, config.embrace.tcp_host, _callbackListener);
//處理 tcp 數據
client.on('data', socketUtils.onData.bind(ctx, client));
這里的socketUtils.onData
是common/common.scoket.js
模塊中暴露出的方法,目的是為了統一處理tcp
句柄中的 data
事件;
在dashboard
模塊中創建的tcp
服務端的回調函數中會再次用到該方法;目的是對相同格式的消息數據格式統一處理;
-
createController
:集成controller
方法
controller
有4種類型: auth、fetch、overview、profiler,對應四種邏輯處理;
dashboard模塊
dashboard模塊初始化和啟動的邏輯與embrace模塊異曲同工,稍有不同的是區分為了兩部分:
- HTTP服務:處理web端用戶的操作,對TCP服務下發指令
- TCP服務端: 處理embrace客戶端發來的消息內容,通知embrace客戶端開始對應的操作
overview
這里以overview為例子走一遍完整的流程
首頁
overview頁面:此頁面可以看到服務器的 CPU 使用率,以及被選中進程的 Memory 占用情況,其中內存占用展示了三類:
- heapUsed: 正在使用的堆內內存大小
- heapTotal: 申請的總堆內內存大小
- rss: 堆外分配的內存大小
整個過程筆者使用了一個簡易的流程圖來示意;(如果不容易理解,請還參考原作者的架構圖)
- web發送 fetchOverview請求
- dashboard http服務 接收到請求后,檢查緩存是否有內容;并且會通知 TCP server 發送消息給 TCP client;最后將消息響應發送回web;
- TCP client 解析消息調用對應controller獲取當前進程cpu占用率以及內存使用情況并寫入緩存,供下次dashboard http服務去讀取信息
上述過程在web端是啟動了setInterval定時器每一秒中請求一次,而且在客戶端每一次都將上一次的數據做了緩存,繪制出了實時的折線圖效果
具體獲取cpu和mem數據是通過下面的方法:
//獲取本進程的 cpu 使用率和 memory 占用信息
const memoryUsage = common.overview.computeMemoryUsage();
const cpuUsage = common.overview.computeCpuUsage();
computeMemoryUsage的計算方法比較直接:process_memoryusage
computeMemoryUsage使用到的Nodejs API:os_cpus并進行了簡單計算
CPU 數據采集分析函數運算瓶頸
這個功能是對CPU-Profiling,然后進行分析,展示結果包含:
- 執行耗費時間大于 500ms(默認值) 的函數列表
- 執行耗費時間最長的 5個(默認值) 函數
- V8 引擎逆優化最頻繁的 5個(默認值) 函數
感謝大佬的開源精神
easy-monitor作者原文鏈接
easy-monitor3.0已經發布。