构建大型Cron系统的思考

之前因为项目需求,看过一些 Python 相关的定时任务实现,当时比较好奇的是它们是怎么解决重复执行问题的。如果部署多个副本,就会产生多次执行问题;如果部署单点可以保证只执行一次,但是高可用又是一个问题。

如果能有一个非常可靠的 Cron 系统,可以:

  1. 保证高可用
  2. 保证正确性(不能重复执行)
  3. 让我很方便地看到所有任务的执行情况,最好是一个 web 界面,不需要登陆机器看日志
  4. 方便配置

就能解决我的一大痛点了。这个想法在以前写爬虫的时候就有了。因为线上的爬虫肯定不是跑一次就完事了,需要每天执行/每周执行,保持追踪目标网站的更新。那时候我们是用的单机部署的 crontab,定时放进去任务,虽说运行了很长时间也没啥问题,但是只能说是运气吧,如果它挂了,我们可能要花很长时间发现、修复。

最近一直在读《Google SRE运维解密》,这本书介绍了各种大型系统的一些指导思想和经验。《24 分布式周期性任务系统》这一章又给了我一些新的启发。

总的来说实现一个高可用、正确的 Cron,我觉得可以有三个思路:

第一种是我在前文中提到的,使用一个分布式的任务队列,将任务的调度和执行分开。然后在定时放入任务的时候根据任务、时间生成一个唯一 token,然后拿 token 去队列里放任务。应该可以解决分布式的问题,即高可用又是幂等的。(只是我的一个想法,还没听说过谁是这么做的,不知道行不行得通)

第二种跟上面的思路一致,也是做调度和执行分离,但是用了外部的数据库保证一致性。(这么实践的公司应该比较多)

实际上上面两种是比较粗糙的,本质上的问题用了简单的冗余。

然后就是我在《Google SRE》中看到的第三种。Google 的 Cron 是用了分布式的方式(我认为这个思路才是正确的,高可用是分布式要解决的一个典型问题。用一个叫做 Paxos 的分布式协议,多个节点保持同步,只有一个节点 master 在工作,将任务的执行情况同步给从节点。如果 master down 了,马上通过选举出现一个新的 master,因为这个 master 一直在和从节点同步状态,所以新 master 也有之前任务执行情况的信息,任务不会被重复执行。Master 在切换时会有很多细节问题需要处理,比如 master 开始执行一个任务的时候,需要把开始执行的信息同步下去,而且必须是先同步再执行,否则执行期间出现了 master 切换,那么新 master 有可能不知道此任务已经被执行了。

这里有一个“部分失败”的问题。其实这个问题比较经典,不仅存在于 Cron。比如 Django 的 migrate 操作,每次失败了都很头疼,要手动恢复数据表重新执行。

《Google SRE》书中提到,要解决“部分失败”的问题,就要实现下面两种至少一个:

  • 所有操作都要是幂等的(这样重复执行不会有问题)
  • 任务的所有操作都可以通过一个外部系统查询所有操作的执行状态(这样切换过后可以针对此任务失败的部分重试)

不幸的是,这两种方案的成本都很大。

正确性和高可用这两个问题,暂时就想到上面三种方案。在我看来,Google 的做法是最好的。

另外还有一个小问题,就是写 crontab 的时候大家都喜欢写在 0 点执行,而文本的 crontab 又很难看到已有任务的分布情况,所以就导致最后 crontab 变得非常集中,0 点的任务特别多。Google 的方法是在 crontab 的语法中添加了 ? 符号,比如如果分钟上面是 ? 就表示任意分钟执行都可以。可以交给系统去调度。

Leave a comment

电子邮件地址不会被公开。 必填项已用*标注