# sptask-monitor **Repository Path**: GeekDot/sptask-monitor ## Basic Information - **Project Name**: sptask-monitor - **Description**: 脚本任务通用监控系统 - **Primary Language**: Python - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2020-07-25 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README

数据平台-任务监控系统

极客点儿
2020-07-25
### 一、项目背景 - 数据平台 `edison` 项目中有100多个任务,每个任务之间都是独立且无耦合。由于任务众多,在执行过程中经常会发生任务中断的情况,为了解决这些任务的中断问题,急需开发一套能实时监控任务状态的监控系统 ### 二、项目需求 - 1. 不能改动现有任务的执行流程和代码逻辑 2. 不能影响服务器上各个任务的正常运行 3. 监控系统要足够轻量,以最简洁的方式嵌入到任务中 4. 任务在启动、停止监控系统时要足够简洁、方便、快速 5. 监控系统的监测频率需达到实时监控的级别 ### 三、项目设计 - #### 设计语言: - Python #### 设计模式: - AOP #### 两大核心模块: - 监控程序:记录模块 & 装饰器 - 监控程序:监控模块 & 守护进程 #### 两大辅助模块: - 监控程序:检测模块 & 心跳检测 - 监控程序:统计模块 & 日常报表 #### 五个工具模块: - 监控路径模块 - 数据库处理模块 - 日志输出模块 - 时间日期模块 - 邮件处理模块 #### 两个存储模块: - DB - pids #### 一个定时任务模块: - crontab ### 四、项目逻辑 - #### 1. 监控项目实现的逻辑细节 ##### 记录模块运行流程 & 功能 因为记录模块的本质是装饰器,装饰器的性质就是可以在函数运行前后进行功能扩展 1. 当一个任务启动以后,任务将 `main` 函数作为参数传递给记录模块并附带一个任务 id 2. 记录模块通过任务 id 从任务列表中拿到任务相关信息,并将任务相关信息初始化 3. 初始化包括:在任务运行详情表中新建一条记录,并将任务信息、任务开始时间、执行状态写入到任务详情中 4. 除了将任务信息写到表中,初始化做的最重要的一件事是:将任务详情的 `id` 和任务的 `pid` 以 `id·pid` 的格式写到文件(文件名称也是以 `id·pid` 命名)中,并存储在 `pids` 目录下 5. 接下来就是监控模块不断地扫描 `pids` 目录并检查任务是否中断,这个到监控模块的时候详细说明 6. 当任务执行完成后,记录模块会先删除 `pids` 目录下当前的任务 `id·pid` 文件,然后将任务结束时间、任务运行时间、运行状态写入到任务详情中 ##### 监控模块运行流程 & 功能 首先监控模块是常驻内存的,是一个守护进程,监控模块启动后,每隔 3 秒就扫描一次 `pids` 目录 1. 监控模块判断 `pids` 目录下是否存在 `id·pid` 文件 2. 如果没有文件,则回到上述步骤 1,等待 3 秒继续下一轮循环 3. 如果有文件,并且任务进程的 `pid` 存在,那么此时数据库状态应该更新`[正在执行]`状态 4. 如果有文件,并且任务进程的 `pid` 不存在,那么此时数据库状态应该更新`[执行中断]`状态,并且发送邮件通知任务中断,最后把 `id·pid` 文件删除 5. 执行完成后,回到上述步骤 1,等待 3 秒继续下一轮循环 ##### 监控系统的监控原理 下面的表格是描述文件和 `pid` 之间的逻辑关系图,是监控系统的监控原理,也是整个监控系统的核心思想 **文件和进程状态:** - `0`:不存在 - `1`:存在 **任务状态:** - `1`:未执行 - `2`:开始执行 - `3`:正在执行 - `4`:执行中断 - `5`:执行完成 接下来就揭晓下面这张表格的秘密,原理很简单 首先文件只有两种状态,要么存在就是 `1`,要么不存在就是 `0`,`pid` 状态也是同理 这样的话,两个参数每个参数都有两种状态,通过排列组合后会得到四种结果 例如:文件`[存在]`,进程`[存在]`,那么结果是`[正在执行]` 有一种情况是不可能出现的,就是下表中第三行。理由是:文件不存在的话,那么进程肯定是不会存在的。因为文件都没有生成,那么程序压根就没执行(不要说权限或者其他的什么问题,这些问题都不属于本次讨论范围内) 详情参考下表: |文件|文件状态|进程|进程状态|执行结果| |:---:|:---:|:---:|:---:|:---:| |F|1|P|1|3| |F|1|P|0|4| |F|0|P|1|error| |F|0|P|0|5| #### 2. 其他辅助工具类模块 上述两大模块就是监控系统的核心功能,但是为了能有更好、更完善、更流畅的应用体验,增加了几个辅助类模块和工具模块 ##### 监控程序:检测模块 刚刚说过,监控模块是一个守护进程,要常驻内存的。但是万一因为某些原因,导致最核心的监控模块断掉怎么办呢?如果监控模块断了,那么就不会监控到任何的任务了,这个问题该怎么解决呢? 其实这也很简单,那就增加一个心跳检测功能,每分钟检测一次监控模块是否断掉,监控原理和监控模块类似,也是判断 `pid` 是否存在 而且检测模块是放在定时任务 `crontab` 中,不用担心检测任务会断掉(当然 crontab 也是一个服务,也会断掉的,具体解决方案放在项目复盘中讨论,在此不做详细说明) ##### 监控程序:统计模块 统计模块就是日常报表,是针对于昨天全天,所有任务的执行详情情况,进行统计、汇总、分析并且定时以表格的形式发送到邮箱 ##### 其他工具模块 为了监控系统编码的完整统一、架构清晰、层次分明、模块独立以及方便日后扩展功能而提供的全局工具类模块,这个不过多做解释 ### 五、项目流程图 - ![流程图](源码及资料/流程图.jpg) ### 六、编码实现 - ![流程图](源码及资料/编码实现.jpg) ### 七、项目中遇到的坑 - #### 1. 处理时间、路径、日志等问题 统一基础功能工具模块,新建 `tools` 包,将需要的基础模块添加到 `tools` 包中 统一路径处理、统一时间处理、统一日志处理、统一邮件处理、统一配置文件、统一数据库处理、统一状态处理 #### 2. 回调id隐患 pymysql 模块是 python 中连接数据库的包,但由于没有自带获取回调 id 的功能,所以没办法直接拿到 id。但是可以通过 `cursor.lastrowid` 获取最后一次插入数据的 id 由于MySQL的特性,每次连接都是启动一个线程,只要不 commit,此时操作后的数据只能本线程看得到,所以获取最后一次插入数据的 id 和其他程序并不冲突,而这一个点会为以后埋下一个巨大的隐患 #### 3. 文件错乱 **出现问题:** 理论上是一个进程是一个唯一的 id 和 pid,修改状态也是各个进程需要改写自己的即可。但是由于当时写文件的时候,每次写入状态都要写一次文件,在写文件的时候加了判断。如果 pids 目录中有文件就忽略,没有文件就写入。但是判断的时候路径是从基础模块路径中拿的,后来改动了基础模块路径,这里的判断就不生效了,所以每次写状态的时候都会写文件。而由于每次都会获取最后一个 id,就导致文件的错乱,最后结果就是数据库中的状态群魔乱舞,状态修改的乱七八糟...... **解决方案:** 不在写文件的函数中进行判断,而是在写数据库时候判断,如果是第一次插入就写文件,除此之外不会再对文件有任何操作,这样就能保证文件是安全的 #### 4. 进程堆砌 **出现问题:** 在测试的时候发现一个问题,程序报错发邮件,状态是 `[执行中断]` endTime 和 runTime 都没有数据,看上去似乎很正常。但是就下来诡异的事情发生了,等了一会儿,这个 `[执行中断]` 的状态居然变成了 `[执行完成]` 状态。后来经过排查发现,原来是 pid 堆砌在一起了 检测是从文件中拿的 pid 进行从进程中中过滤(grep),例如:拿到的文件 pid 是 9527,但是操作系统中的进程很多,可能拿到了 3 个 pid,分别是:9527、95271、95279 当时程序进行判断的是用 `==`,而且从系统中拿的都需要进行特殊字符清洗,所以结果就成了 if db_pid == os_pid: pass 添加数据后: if 9527 == 95279527195279: pass **解决方案:** **1. 用路径 + 文件的方式过滤** 可能导致路径问题和其他问题 **2. 使用列表(数组)** if db_pid in os_pid: pass 添加数据后: if 9527 in [9527, 95271, 95279]: pass #### 5. 临界状态 **出现问题:** 程序执行完成后,没有中断,正常情况下状态为 `[执行完成]` 才对,但是诡异的出现了程序 `[执行完成]` 但状态还是 `[正在执行]`(不管什么样的微观世界都很诡异,量子世界就如此诡异) 所谓临界状态:是考虑程序在程序微观世界内,发生在几微秒的时间内,遇到所有有可能问题 监控模块可能在第 10μs 的时候拿到了任务的 pid,在第 11μs 的时候记录模块可能执行完成释放了 pid,并将数据库的状态改为[执行完成],而此时的监控模块拿到的还是 10μs 时候的 pid,没来得及进行下一轮判断就直接将此,时数据库的状态[执行完成]改为[正在执行],这样就导致一个悖论出现,数据是执行完成后的数据,而状态却是运行时候的状态...... **解决方案:** 在[执行完成]和[执行中断]写入状态之前,需要进行判断。如果[执行完成]和[执行中断]要写入状态时,则需要判断数据库中不是[执行完成],这样才不会改乱状态 #### 6. 实时监控 所谓实时监控:是不设置延时时间,sleep(0) 这样会导致频繁读取硬盘,影响正常任务,增加状态判断,是为了监控系统在临界状态、及时监控的时候都能保证写入数据库的状态是正确的 #### 7. 析构顺序 对于宏观程序,当任务执行完成时比如需要删除文件和改写数据库状态,这时候其实先操作那个都可以。但是对于微观程序,必须先删除文件,要不然监控模块会拿到文件 pid 却找不到进程从而出问题 #### 8. 监控没有及时报警,小米事件(邮件只发给我自己) #### 9. 添加任务监控时,有语法错误(把全局变量放入局部变量之中) ### 八、项目复盘 - #### 项目的成果: 已开发完成一套稳定、实时监控、功能完善的监控系统 #### 项目的优势: 1. 能实时稳定的监控所有服务器上正在运行的任务 2. 能快速追踪定位到中断任务的详细信息并及时发送报警邮件 3. 完善的监控日志、监控自检、邮件报警、任务执行记录的日常报表 #### 项目的不足: 1. 监控系统只能追踪定位到任务中断的详细信息,具体中断的原因无从得知,因为中断原因需要日志系统完成。就目前来说监控系统还没有发挥出最大的威力,只发挥出一半的威力 2. `crontab` 是整个监控系统和数据平台项目的核心,cron 中断的问题还没有解决。不过已有解决方案,即使用服务器集群分布式监测。另一种解决方案就是将整个项目重构,将所有的任务都作为主程序的子进程,这样就可以有主进程得知子进程(任务)的运行状态 ### 九、个人复盘 - #### 个人的优势: 1. 能独立解决问题 2. 能快速定位并且修复程序中出现的BUG #### 个人的不足: 1. 经验不足,导致刚开始好多细节没有考虑到 2. 对公司业务流程和数据平台任务不熟悉 3. 细节处理不够完善,暂时还达不到完美效果 #### 需要的提升: 1. 提升专业技能,数据库知识和数据分析能力 2. 理解公司业务流程和数据平台相关任务 3. 严谨对待每一行代码,对代码有敬畏之心,克己律人 #### 后续的规划: 1. 理解数据平台的所有相关任务,重构数据平台任务 2. 开发数据平台日志系统