介绍

Linux服务器中总是运行着诸多定时任务,任务的调度常借助Linux crontab完成。当任务变多,手工的监控日志、启停任务变得复杂。

虽然市面上已有许多完善的任务调度服务,但是对于我们初学者来说,为了锻炼代码能力,尝试自行解决问题,造一造轮子,实现一些demo,也并非坏事。

注: 文章代码参照课程(https://coding.imooc.com/class/281.html )实现,并上传至(https://github.com/k-si/crongo 求个star~ otz)

分析

当面对成百上千个crontab任务时,我们希望(1)有可视化的平台,方便的创建、删除、中断任务(2)并且可以实时查看任务产生的日志,了解任务的调度时间,执行时间,输出内容(3)当任务执行时间过长,或因失败导致中断,服务可以及时发送邮件、短信等报警(4)可以实现横向扩展,多个节点同时竞争调度,不仅仅限于单机执行。

面对这些需求,我们给出了如下解决方案:

  1. 保留crontab表达式,以此确定执行周期。但是任务的调度算法自行实现,不依赖于Linux内部的守护进程。

  2. 采用master-worker架构,master可看作http服务,多worker竞争执行到期任务。

  3. 任务信息及日志分别采用etcd和mongodb存储。

简要的架构图如下:

image

关于架构的解释:

  1. master节点用于处理http请求,worker节点用于任务的调度执行。

  2. master与worker节点之间不直接交互,都由etcd间接通信。例如任务的修改,只需master写入etcd,worker通过watch机制捕获更改。

  3. 任务日志由worker写入mongodb,再由master读取。

这样设计架构的好处是:

  1. worker可以分别部署在多台机器上,同时读取etcd中的任务信息并进行调度。

  2. worker节点之间不进行通信,依靠etcd实现分布式锁控制任务并发调度。

  3. worker节点的崩溃恢复不影响整个系统服务。(没有正常的worker节点,系统整体仍可接受外部请求)

实现

在master目录下,主要用作http api接口的处理,直接面向etcd进行crud操作,日志也是直接面向mongodb读取。

worker目录下是核心的job执行逻辑。从文件名可以看出,分为这么几个模块:

  • connector,连接模块,操作etcd和mongodb
  • watcher,监控模块,监视etcd中键值变化
  • scheduler,调度模块,对内存中维护的任务表进行轮询调度到期任务
  • executor,执行模块,到期的任务执行其cmd命令
  • logger,日志模块,封装任务输出的日志,交由mongodb存储
  • register,注册模块,负责worker节点的健康检查,可由master查询当前的节点信息
  • lock,分布式锁模块,处理多worker节点同时竞争任务

其中关键的任务执行流程是 watcher -> scheduler -> executor -> logger,这几个模块各自启动一个goroutine,模块间通过channel通信。