背景#
组内现有的监控数据主要分为 系统日志数据 与 业务埋点数据,分布在对应两个平台上,在系统上线后或是业务高峰期时研发同学需要持续关注线上数据来预判风险,两个平台来回刷新操作频繁,体验不佳。
思考#
- 两个平台来回查看麻烦,可以考虑整合不同平台的数据源做处理
- 将主动查看变为被动感知,进一步提升体验
问题拆解与方案#
- 针对第一点,不同平台提供了对应的指标查询 API ,这部分主要考虑数据源的定义、配置与整合
- 基于指标查询 API,在自研监控平台站点上新增了一个业务侦查配置模块,用户可以以项目维度配置对应不同平台的监控指标
- 针对第二点,在做好数据源的配置整合后还需要实现数据的推送,内部 IM 工具也提供了开放平台 API 可接入调用
在常规的数据推送之外,还需要支持用户自定义周期的订阅推送,使用 node-cron 来生成对应的定时任务,一个订阅配置对应一个任务,用户更新了订阅配置,对应的任务也需要更新。
目前方案拆解下来还是比较直观清晰的,但问题在于我们自研监控平台服务是走的 PM2
同时会启动 4 个进程,以及线上集群至少是 3 台机器,同时在运行的 node-cron
任务至少是 12 个,因此又出现了 多进程间的周期任务管理 的问题。
从整体的负载均衡考虑,允许多进程注册多任务,但要保证只有一个任务会被执行,以及用户修改配置的请求会打到其中任意一个进程,也需要保证剩余进程同步注册最新任务。
多进程管理方案#
从后端的角度看很明显是需要实现一个分布式锁,最常见的方式就是使用 Redis
的 setnx
命令,但内部监控平台站点目前只使用到了 MongoDB
做存储,秉承尽量不引入新依赖的原则仔细思考了一下,发现 MongoDB
自带的原子性操作也能做到,详细流程如下
核心逻辑是设计了两个标志位:
- 有效标志位解决多进程同步问题
- 运行标志位解决多进程并发问题
标志位的规则是 IPv4
地址 + Node
进程号,其中进程号可以直接通过 process.env.NODE_APP_INSTANCE
获取,而 IP 地址需要借助内置的 node:os
模块
import { networkInterfaces } from 'os';
/**
* 返回 ipv4 地址
*/
export const getIPAddress = () => {
const interfaces = networkInterfaces();
let address = '';
for (const devName in interfaces) {
if (Object.prototype.hasOwnProperty.call(interfaces, devName)) {
const iface = interfaces[devName] || [];
iface.forEach((alias) => {
if (
alias.family === 'IPv4' &&
alias.address !== '127.0.0.1' &&
!alias.internal
) {
address = alias.address;
}
});
}
}
return address;
};
最终利用标志位的设置很好地解决了多进程管理的问题。