前言
在许多业务场景中,需要定时处理一些业务任务,比如菠菜现在负责的智能调度系统,需要定时的执行调度任务来给运力进行推单。那业内也有很多解决方案比如AMQ的延时队列等,今天菠菜来聊一种简易的、可靠的、同时性能也很高的设计方案。
总体思路
废话不多说,先上图:
总体的设计思路是本地建立Timer + 远程备份的方式。比如上图,服务器1建立本地Timer,并将定时任务所需要的数据+触发时间点存储到一个中间存储用于备份。正常情况下本地触发Timer并执行定时任务,执行完后删除远程备份。对于异常情况下(如某一台服务宕机了),每台服务器会额外建立一个定时任务,这个定时任务会定时检测远程备份中是否有需要恢复的定时任务,若有则获取相关数据并进行执行。
具体如何实现
本文以Golang + Redis为例,具体描述一下该如何落地。
建立并备份定时任务
对于某一个分布式调度任务,需要提前设定好TimerCallback(即回调方法),Redis链接实例(即文中的RecoverRedis),以及远程备份的存储的key(即文中的RecoverID)。
那我们再来看看建立定时任务需要的参数:
data,回调方法时,需要传入的数据
delaySeconds,延时时间点,即n秒后执行该任务
recoverSeconds,任务执行n秒后还未完成就算任务失败,并且需要进行恢复。
这里需要详细说一下recoverSeconds的作用,它会作为判断任务是否正常被执行的重要依据。若一个定时任务为5秒后执行(delaySeconds),并且最大执行时间为10秒(recoverSeconds)。15秒后,若其他服务器发现该任务还在备份列表中,则认为原有任务执行失败,需要进行恢复。
还如上图,若执行完定时调度,则会将备份进行删除。
异常情况恢复定时任务
每台机器都会建立一个Ticker,每隔一段时间去Redis中查询超时的任务,若发现有超时的任务,则进行争抢,若争抢成功则进行恢复。
由于备份任务的时候采用的是Redis的zset(如zadd key timeoutTime data)。那获取超时的任务就比较简单了,使用zrangebyscore key 0 now即可将超时的任务获取到。至于争抢的过程,需要利用分布式锁,这里就不多介绍了,之前的文章里有提到。争抢到后,调用之前设置的TimerCallback方法并将data传入进行调度任务的恢复。
至此,简易的分布式调度设计思路阐述完成。其实在实际业务场景中还可以对该设计进行微调,比如在调度任务执行过程中不断给备份内容进行续命,而不是写死一个recoverSeconds等。
最后
好久不写公众账号了,趁着年前略轻松,抽时间写篇文章,也以这种方式给大家拜个早年!祝大家新年快乐!
领取专属 10元无门槛券
私享最新 技术干货