NodeJS服务监控报警系统的核心实现和开源共建

NodeJS 服务监控报警系统的核心实现和开源共建

  1. 建设服务监控系统的初衷和历程
  2. 核心实现思路及实际应用
  3. 现有功能点介绍和路线图
  4. 代码半开源的目的、愿景
  5. 开源代码的结构和部署拓扑图
  6. 项目开发者的个人介绍

建设服务监控系统的初衷和历程

随着基于 NodeJS 前后端分离方案的推行,前端的开发模式和角色也在发生着悄无声息的变化,而今 NodeJS 的开发俨然已经成为我们日常工作中的一部分,前端工程师与服务端、运维都有了更多的交集,但随着业务和项目的扩张,生产环境 Node 服务也在不断增多,如何对这些服务的运行状态和各项指标了如指掌是当前我们大家共同遇到的挑战。

我的初衷是建立一个专门针对 NodeJS 的服务监控平台,它能够支持服务集群的统一管理和多用户登录,旨在帮助团队的开发者、架构人员以及管理者能够直观的观察线上各个服务的实时状态,并能够帮助开发者及时发现线上服务的异常情况(比如内存泄露、服务崩溃重启、慢路由等)。

PM2 是一款非常优秀的 Node 进程管理工具,它有着丰富的特性:能够充分利用多核 CPU 且能够负载均衡、能够帮助应用在崩溃后自动重启、能够监控资源的使用情况并且支持 API 方式查看、并且有配套的 Keymetrics 可以用于服务的监控。相信不少开发者都在使用 PM2 部署自己的 NodeJS 应用,起初也曾尝试使用 Keymetrics ,但 Keymetrics 是一款商业服务且价格不菲,虽有两台服务器的免费配额但对于有着众多服务器的团队或者公司而言无异于杯水车薪,而且对于国内的大公司而言都会考虑到自身的数据敏感性不希望服务的运行状态接入到第三方平台中,当然如果你的服务器数量很少,又或者能够支付昂贵的使用费用,而且无需关心数据的安全问题依旧推荐可以使用 Keymetrics ,毕竟它是 PM2 的开发者的开发和维护,而且功能特性也很丰富。

但我遇到的场景是如何实现一套通用的监控系统,它能够独立部署而且方便进行扩展和定制化,显然在现阶段使用 Keymetrics 不是十分明智的决定,于是便想到利用 PM2 的 API 进行服务监控,但 API 的方式不利于多台机器的统一管理和聚合,而且通过 API 的方式会将服务的详细信息泄露到外网,有一定的安全隐患。之后便开始设想是否能够基于 PM2 进行一些二次开发,实现一个核心功能并具备一定定制性的系统(类似 Keymetrics ),一方面可以为整个行业的 Node 推广起到一定的积极作用,一方面能够确实帮助到我们团队的服务运维。我将这个项目命名为 PM25 ,意即基于 PM2 的基础设施多做了一点,仅此而已别无他意。

现有功能点介绍和路线图

  • 支持用户的管理(登录作为中间件)
  • 被监控机器的分桶管理
  • 机器列表、快速过滤和主机的指标信息(进程数、 CPU 数、负载、上线时长、内存占用)
  • 进程的详细指标信息( PID 、进程名、重启次数、上线时长、状态、 CPU 占用、内存占用、错误日志)
  • 同 Falcon 整合,支持监控报警管理(核心指标同步 Falcon ,可以查看历史图表或者配置监控报警)
  • 支持扩展包,引入扩展包后可以收集统计服务端慢路由信息
  • 支持进程的远程控制,可以在云端对进程进行远程操作(比如重启、重载)

代码开源的目的、愿景

我的初衷很简单,一方面能够把自己在工作之余的贡献拿出来和行业分享,也希望绵薄之力能够对行业的 Node 推广起到一些作用,反过来也希望行业内的济济人才能够参与到它的建设中来,基于我目前的成果和代码进行更多的功能开发和细节完善,把大家对 Node 的热忱和对 PM25 的建设形成合力为行业带来一些影响或是价值,代码托管于Github,项目地址为PM25.io

开源代码的组成结构

├── PM2.5-Cli           // PM25 CLI
├── PM2.5-Cloud         // PM25 云端控制台
├── PM2.5-Service       // PM25 服务端代码
├── PM2.5-Docs          // PM25 文档
└── PM2.5-Ext           // PM25 扩展包

数据库组成和分表结构

├── pm25                // 主库,用于监控主机的信息存储
│   ├── buckets         // 桶表,用于存储主机分桶信息
│   ├── events          // 事件表,用于存储主机的事件信息
│   ├── exceptions      // 错误异常表,用于存储主机的线上报错
│   ├── monitorings     // 监控信息表,用于存储主机的整体监控数据
│   ├── status          // 主表,用于存储主机的各个进程数据
│   └── transactions    // 网络传输表,用于存储慢路由相关信息
├── pm25-session        // 会话信息库,用于存储 SSO 登录后的用户会话
│   └── sessions        // 会话信息主表,用于存储用户会话详情
├── pm25-test           // 测试表,用于开发过程中的测试

部署拓扑图

使用截图

项目作者的个人背景介绍

郭凯,工作狂、强迫症,崇尚工匠精神,全栈工程师,翻译作品有《编写可维护的 JavaScript 》、《第三方 JavaScript 编程》。开源项目有InJuicerjSQL、以及开源前端技术社区F2E等,欢迎关注我的微博个人博客、或者加入前端技术社区,随时同我进行技术交流。

Node.js的process模块

process模块用来与当前进程互动,可以通过全局变量process访问,不必使用require命令加载。它是一个EventEmitter对象的实例。

属性

process对象提供一系列属性,用于返回系统信息。

  • process.pid:当前进程的进程号。
  • process.version:Node的版本,比如v0.10.18。
  • process.platform:当前系统平台,比如Linux。
  • process.title:默认值为“node”,可以自定义该值。
  • process.argv:当前进程的命令行参数数组。
  • process.env:指向当前shell的环境变量,比如process.env.HOME。
  • process.execPath:运行当前进程的可执行文件的绝对路径。
  • process.stdout:指向标准输出。
  • process.stdin:指向标准输入。
  • process.stderr:指向标准错误。

下面是主要属性的介绍。

(1)stdout

process.stdout用来控制标准输出,也就是在命令行窗口向用户显示内容。它的write方法等同于console.log。

1
2
3
exports.log = function() {
process.stdout.write(format.apply(this, arguments) + '\n');
};

(2)argv

process.argv返回命令行脚本的各个参数组成的数组。

先新建一个脚本文件argv.js。

1
2
3
4
// argv.js
 
console.log("argv: ",process.argv);
console.log("argc: ",process.argc);

在命令行下调用这个脚本,会得到以下结果。

1
2
node argv.js a b c
# [ 'node', '/path/to/argv.js', 'a', 'b', 'c' ]

上面代码表示,argv返回数组的成员依次是命令行的各个部分。要得到真正的参数部分,可以把argv.js改写成下面这样。

1
2
3
4
// argv.js
 
var myArgs = process.argv.slice(2);
console.log(myArgs);

方法

process对象提供以下方法:

  • process.exit():退出当前进程。
  • process.cwd():返回运行当前脚本的工作目录的路径。_
  • process.chdir():改变工作目录。
  • process.nextTick():将一个回调函数放在下次事件循环的顶部。

process.chdir()改变工作目录的例子。

1
2
3
4
5
6
7
process.cwd()
# '/home/aaa'
 
process.chdir('/home/bbb')
 
process.cwd()
# '/home/bbb'

process.nextTick()的例子,指定下次事件循环首先运行的任务。

1
2
3
process.nextTick(function () {
console.log('Next event loop!');
});

上面代码可以用setTimeout改写,但是nextTick的效果更高、描述更准确。

1
2
3
setTimeout(function () {
console.log('Next event loop!');
}, 0)

事件

(1)exit事件

当前进程退出时,会触发exit事件,可以对该事件指定回调函数。这一个用来定时检查模块的状态的好钩子(hook)(例如单元测试),当主事件循环在执行完’exit’的回调函数后将不再执行,所以在exit事件中定义的定时器可能不会被加入事件列表.

1
2
3
process.on('exit', function () {
fs.writeFileSync('/tmp/myfile', 'This MUST be saved on exit.');
});

(2)uncaughtException事件

当前进程抛出一个没有被捕捉的意外时,会触发uncaughtException事件。

1
2
3
4
 process.on('uncaughtException', function (err) {
console.error('An uncaught error occurred!');
console.error(err.stack);
});

内容来源:http://javascript.ruanyifeng.com/nodejs/basic.html#toc22

Node.js的Path对象

NodeJS中的Path对象,用于处理目录的对象,提高开发效率。
用NodeJS的Path命令,与使用Linux下的shell脚本命令相似。
引入path对象:

1
var path = require('path');

比较实用的方法:

格式化路径 path.normalize(p)
特点:将不符合规范的路径格式化,简化开发人员中处理各种复杂的路径判断

示例:

1
2
path.normalize('/foo/bar//baz/asdf/quux/..');
=> '/foo/bar/baz/asdf'

继续阅读