如何实现一个定时任务调度系统

任务调度系统的功能是定时的执行一些预设的任务,这个功能单从逻辑上看是比较简单的。比如,一个简单调度系统可能是在单个节点上运行一个这样的程序:

while true {
    check_and_execute_task()
    sleep(time_duration)
}

这个方式实现的调度系统有很多问题,比如说,如果这个节点故障了该怎么保证任务能够得到执行,或者说如果任务数量特别大,单个节点的存储能力不够,执行任务的cpu也不够,又该怎么处理。因此,为了使调度系统高可用,同时能够应对海量的任务,必须实现为分布式的架构,以此来消除单点,提升存储和计算能力。

调度系统的实现可以分为两个大的部分,一个是调度的逻辑,一个是任务的存储。我们分别来讨论一下。

调度逻辑

第一个要考虑的就是解决单点问题,将调度程序部署在多个节点上,以此保证即使某个节点故障,其他节点上的程序可以继续调度执行任务。如果按照这样的设计,调度程序必须是无状态的,加入一个新的调度程序实例到系统中,从现有系统中退出一个调度程序,应该都是对整个系统的运行没有影响的。按照这个思路,我们可以把读个调度程序做成类似消费者的机制:假设我们有一个任务池,其中记录着所有需要执行的任务,多个调度程序从任务池中争抢任务来执行。作为消费者,单个调度程序的故障不影响其他调度程序的执行。

另外一个问题是:在海量任务的情况下,如何保证所有到期的任务都能够尽快的得到执行。这个问题比较复杂,我们暂时只讨论两种情况。

不考虑优先级,将所有的任务看做一类。这样的话,假设到某一时刻需要执行的任务有一千万,那么按照调度程序的数量,将任务均分给这些调度程序就可以了。如果说还是感觉执行的太慢,可以增加调度程序的数据量(前提是cpu还有余力)。

考虑平等问题,也就是需要保证不同业务产生的任务都能够尽快执行。比如说,现在系统中的任务是由两个业务模块产生的。业务A产生的定时任务有一千万条,业务B的定时任务只有100条,然后他们的任务执行时间是相同的。那么到执行的时候,可能出现一个情况:所有的调度程序都去执行业务A的任务了,业务B的任务则长时间得不到执行。
解决这个问题的思路是将调度程序和业务绑定起来,比如说设置两个调度程序专门去执行业务B产生的任务,只有不存在业务B的任务时才去处理其他任务。这样就保证了每个业务的任务都能够得到公平的执行机会。(这个方法会使得业务模块和调度系统耦合,不过这不算是个大问题,毕竟耦合这个东西在大多数实际工程项目都是普遍存在的-_-)。

任务的存储

每一个任务都是需要持久化存储的,这一点毋庸置疑。此外,由于任务都是定时执行的,所以一个直观的思路是按照时间顺序来存储任务,或者说,使用一个能够提供按照时间顺序来查询数据的数据库。mysql首先排除,因为mysql是单机数据库,支持的容量太小,如果使用分库分表的方法,那么多个表之间也没法保持时间顺序。首选的数据库应该是分布式数据库,同时key是有序的,比如hbase,tikv等。此外,如果考虑上面提到的按业务来调度,那么还需要能够按照业务来查询任务,也就是支持两个维度的查询:时间、业务,在hbase、tikv这样的数据库中,通过设置key的格式,很容易达到这个目标。

另外一个需要考虑的问题是,如何保证任务状态的一致性。比如说一个任务现在已经执行完成了,需要从数据库中删除这个任务,如果此时调度程序崩溃了,可能导致任务被重复执行。解决这个问题有两个思路:1、将任务执行完成和删除任务的动作放到一个事务中,即要么执行完成+删除任务同时操作,要么都不操作。这个目前看是没法实现的,在分布式的架构中做事务一直是个困难的事情,更不用说这里任务的执行可能是在其他系统中进行的。2、保证每个任务的执行都是幂等的,即重复执行一个任务对业务无影响。实现幂等需要业务模块来做,不过相对来说,这个方式更容易。

你可能感兴趣的:(如何实现一个定时任务调度系统)