前言
笔者之前有使用轻量级的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已经发布。