一、任务系统
图 1
图 2
说任务系统的话大家想起的就是要执行任务了,什么时间什么地点做什么事情。图 1 是简单的任务,在每天 8 点输出 Hello Gophers!,大家用的比较多的任务系统就是 crond,其实这个系统(图 2)也很简单,就是守护进程加一个任务列表。
|早期的 cron
说一下早期的 cron,早期的时候(大概在80 年代)在 Unix 系统里面 cron 运行逻辑
1.读 /usr/lib/crontab 文件
2.如果有命令要在当前时间执行,就用 root 用去执行命令
3.每秒钟去运行一次,有就执行,没有就重复,比较简单
|支持多用户的 cron
-Unix System V,1983
1. 启动的时候读取所有用户下的 .crontab 文件
2. 计算出每个 crontab 文件里需要执行的命令的下一次执行时间
3. 把这些命令按下一次执行时间排序后放入队列里
4. 进入主循环
计算队列里第一个任务的执行时间与当前的时间差
Sleep 直到第一个任务执行时间
后台执行任务
计算这个任务的下一次执行时间,放回队列,排序
后面发展成支持多用户的,这时候的执行算法改了。这相当于按照时间顺序排列,谁先执行的就放在那里,计算现在的时间与下个时间间隔,再让程序暂停,需要时唤醒程序执行,然后重复。
|近代的 cron
Linux,1991
1. Vixiecron(PaulVixie1987)
2. Version3Vixiecron(1993)
3. Version4.1ISCCron(2004)
4. anacron,dcron,fcron
现在的话 90 年代,大多数的都是用 4.0 的版本。大家用了这么久,基本都是在一些单机上跑一些任务,也是这样的操作。功能很简单,一个时间的规则,一个命令,如果公司有很多服务器的话,维护起来就不方便了,必须得一台一台去管理的或者是用脚本管理,多机器的情况下任务维护成本较高。
而且如果单机的话,如果失败了之后怎么办?就无法继续了,如果能做成分布式的就好了。
二、分布式任务系统
|分布式任务系统特点
分布性
对等性
并发性
缺乏全局时钟
故障总是会发生
分布式系统有什么特点呢?大概的特点就不延伸了,大家可以理解为把一个简单的软件放在多台机器上,然后多台机器是都可以完成这个任务的。那么分布式的 crond 的问题,怎么解决呢?
图 3
第一个就是把守护进程做成多个分布式的进程(图 3),把命令分布在数据库里面,这是最简单的实践方式。
|市面上的一些任务系统
Azkaban
Chronos
Airflow
dkron
swoole-crontab
Saturn
现在我们也想用一个这样的东西,一开始的时候找了市面上的方案,如上述现在市面上有很多方案,国内国外都有,最后一个是唯品会弄的,我挑几个讲一下。
1.Azkaban 是一个 Hadoop 上的任务管理,提供功能清晰,简单易用的 Web UI 界面,也提供 job 配置文件快速建立任务和任务之间的依赖关系等等好处,但只能对 Hadoop 做任务管理,不符合我们的要求,我们的要求是替换单机版的 crond。
2.Chronos 适合在容器编排管理工具上的调动,里面全部是替代了 crond,里面有 UI,也有灵活的调度,但是要依赖于容器,对于我们来说应该还有更好的方案。
3.Dkron 这个就比较接近我们想要的东西,比较简单,高可用,也可控。但是不好的就是对任务的支持比较简单,而且都跑在容器上,相当于替换成容器。因为我们的机器很多都是本地任务,需要无缝替换 crond,因此也不考虑。
|需求
对很多任务系统进行考察之后,我们有一下需求:
可替换 crond
分布式、高可用
支持多种任务属性
易用
易部署
因此,我们开发了 cronsun。
三、cronsun
图 4
图 4 是 cronsun 的系统架构,简单来说就是把任务存储在一个分布式 etcd 里,单个 crond 部署成一个服务,也就是node.1\2\3等等部署下去,再由 web 界面去管理。
|cronsun 特性
部署简单
Web界面统一管理任务
任务失败重试
任务失败邮件提醒
多机单任务(防止单机挂掉任务不按时执行)
单机任务并行数限制
执行单次任务
多机器严格的时间间隔任务
支持安全性配置,可以限制任务脚本的后缀和执行用户
....
现在看一下 cronsun 的特性。首先第一点就是部署简单,只需要在每个机器上部署 crond 节点,再部署一个界面管理就可以了。如果 cronweb 挂掉了,只是这个界面不能对任务进行更新,但是不影响任务节点的运行,也就不影响任务的执行。其他的任务属性也讲了,最主要的是对任务有几种任务类型,就是可以在单台机器替换 crond ,也解决单点的问题。比如说某个机器死了另外一台机器就会执行这个任务,保证任务执行。
|cronsun 主要组件
图 5
cronsun 主要有 3 个主件,都是通过 etcd 通讯的。cronnode 负责节点的分组及节点的状态,croeweb 是管理任务的、执行结果都可以在上面看。
|任务类型
普通任务,和 crontab 中的任务一样
单机单进程任务,普通的 crontab 任务是单机的,如果执行任务的机器出现问题,任务可能执行失败。 cronsun 提供此任务类型是保证有多台机器可以执行一个任务,但在执行任务被执行时,只有一台机器在执行任务
一个任务执行间隔内允许执行一次,这个类型的任务和单机单进程任务类似,但限制更严格。因为多台机器间,时间可能会不一致,而某些任务要求执行的时间间隔要严格一致时,可以考虑采取这种类型
刚才说的任务就是这 3 种类型,单机单进程是只想在一台机器上执行,有 n 个节点,但是只会在一台机器上执行结果。此外还有一种情况,假如有3台机器,每台机器的时间不一致的话,这种任务保证在一个时间间隔内,不管机器时间差怎样,保证只执行一次,不重复执行,解决机器时间不同步的情况。
|任务属性
失败重试
超时设置
安全设置
同时执行任务数设置
分组设置
任务的属性也大概说一下,如果随便填任务的话,web 任务界面暴露出去人家随便填几个任务就可以攻击你,安全设置只要加了脚本的后缀(就是本机器有的脚本),假如你的机器是安全的,那么整个系统就是安全的。还有同时执行任务数量设置,还有分组设置便于方便管理的。
|任务定时器
图 6
定时器,跟 crontab 是一样的,只是多了一个秒级。crontab 便于集中管理,多台服务器的时候就比较方便,保证了不会单点失败。还有就是简单的任务调度,web 是通过 API 来调度的,如果想使用的话稍微看一下 API 就可以调用了,整个系统大概就是这样。
四、心得体会
说一个开发过程中的体会。
第一个是方案的选择,方案选择一开始的时候基本都有这样的问题,你到底要做什么东西,系统要搞很多东西,我想调度,我想备份,能达成多种任务的系统里面。另外一种就是我把我想要的东西某个点做的很好,基于我们的人力物力,而且我们做的足够好,至于后面想扩展的时候能够扩展就可以了。
|timer 的使用
图 7
如图 7 是一个典型的运用场景,我先跑一个任务,假如这个任务超时了就设置超时。在监听的过程中,有任务就执行,没任务就执行超时处理。
图 8
刚才的使用只是单次的话是没有问题的,但是在 for 循环里面会每次循环的话都会新建一个timer,这样的情况在要求性能比较高的场合,这是不可行的。有什么替代的方法呢?
图 9
图 10
熟悉的小伙伴应该就会想到直接做一个 tick(图 9 ),自动给出消息,但是这个代码有一些问题,例如上面的代码,这个代码假如 1 秒之后没有了就超时。有个执行(图 10),如果你在这里可以直接刷新的话,这个超时的时候不会刷新的。
图 11
如果想超时也能刷新的话就需要做一些改造(图 11)。改造后就会刷新它,来的时候就会刷新超时,处理完了第一个,再处理完第二个,这样就可以解决 timer up 的问题。
|goroutine 退出
图 12
第二个是 goroutine 的退出,大家可能有这种习惯就是每当起任务的时候都会起 goroutine ,这是不好的习惯。解决的办法是就是限定一定的 goroutine,限定 goroutine 的数量而防止流量暴涨的后续问题(图 12),这是比较好的方案。
大家可以看一下 goroutine 退出的机制,每起一个 goroutine ,就起一个监听,退出的时候就等待 goroutine 退出,再结束。
图 13
这里有个问题,这种方式只适合 goroutine 一跑就结束的形式,并不能通过信号去让 goroutine 的退出。如果要通过信号控制 goroutine 的退出怎么办呢?需要有一个等待的函数(图 13),就是退出接受信号的函数。
图 14
具体是怎么实现的呢?如图 14 函数监听退出的信号,goroutine 接到信号就直接退出,在外面的时候就等待,整个退出机制就很完善了。
图 15
图 16
但是以上情况是如果有一个信号退出的时候所有 goroutine 就会退出了,如果想实现链式的退出怎么办呢?就需要使用到信号传递了(图15)。可以这么做(图16),如图信号传递在第一个结束后再做传递,第二个监听完退出再传递,如此类推到所有退出完毕,就可以实现链式的有秩序退出。
|语言的选择
最后再来说一下为什么选择 Go 语言来做开发语言,总的来说可以总结为以下几点:
简单、开发效率高
部署简单,只需一个二进制文件,无依赖
适合自己的才是最好的
一是我们看中它容易学习,开发效率高,运营也不错。两个人在工作之余,相当于工作的 20% 的时间,团队一个月就弄了一个版本出来,我们觉得还不错。
二是部署很简单,部署的时候就是二进制,很小没有依赖,服务器只要不是太老的系统部署十分简单轻便。
最后一个就是适合自己的才是最好的, Go 语言对我们公司来讲实现的成本是最低的,是最适合我们的,在我们的眼里是最好的