类cron的循环任务调度器设计
假设你想安排一些定期执行的任务,比如:
- 每周三上午10点发送邮件
- 每个月的第一天创建总结
而且你希望在一个网络应用中为合理数量的用户安排这些任务,比如有10万用户,每个用户可以决定他们想要在什么时候安排什么任务。
你还希望确保这些安排的任务能够执行,即使它们最初没有按时执行。例如,如果因为某种原因,邮件没有在周三上午10点发送,那么它应该在下一个检查时间,比如周三上午11点发送。
那么你会怎么设计这个系统呢?
如果你使用cron(定时任务工具)每隔一段时间触发你的调度应用,那么有什么好的方法来实现决定每个时间点应该执行什么任务的部分呢?
我见过的类似cron的实现方式是比较当前时间和所有指定任务的触发时间,但我想处理那些错过的任务。
我感觉可能有比我想到的更聪明的设计,所以请给我一些启发吧。
3 个回答
使用一个支持Java的进程,并结合Quartz调度器,可能是一个不错的解决方案。我觉得Quartz在这个规模上应该能表现得相当不错。你可以看看这个相关的问题:“如何扩展Quartz调度器”...
如果你仔细阅读Quartz的文档,你会发现关于触发和错过执行的担忧都有很好的解决方案,并且提供了多种合适的选择。在扩展性方面,我认为你可以把任务存储在一个JDBC支持的数据库中。
被划掉了,因为提问者特别想讨论设计方面的问题...
如果你在提问之前,先用“Python的任务调度器”来搜索StackOverflow,你会找到这个:“一个适合Python的企业调度器...”。我强烈建议你寻找现有的实现,而不是试图自己从头开发这样的东西,尽管其他回答中提到的做法很不错。考虑到你提到的扩展性目标,这个任务相对比较复杂,你应该在深入研究这个已经发展得很成熟的话题之前,先排除所有其他选项。一个可以考虑的方向是通过Jython来适配广受好评的Quartz
,看看你的使用场景是否能在这种情况下得到处理,尽量少接触Java的部分(这可能不是你首选的方式)。
如果你想直接开始使用而不想花时间设计,可以看看Celery。它的调度器叫做celerybeat。
补充:还有相关内容:如何每周发送100,000封邮件?
基本上有两种设计。
一种是定期运行,比较当前时间和预定的时间表(也就是“现在该运行了吗?”),然后执行符合条件的任务。
另一种方法是查看当前的时间表,找出下一个应该执行的时间。然后,它会把当前时间和所有“下一个时间”早于“当前时间”的任务进行比较,并执行这些任务。完成一个任务后,它会重新安排这个任务的下一个执行时间。
第一种方法无法处理“错过”的任务,而第二种方法只能处理之前已经安排好的任务。
具体来说,假设你有一个每小时运行一次的计划,在整点执行。
比如说,1点、2点、3点、4点。
在1:30的时候,任务调度器停止工作,没有执行任何进程。直到3:20才重新开始。
使用第一种方法,调度器会执行1点的任务,但不会执行2点和3点的任务,因为在这些时间点它没有运行。下一个要执行的任务将是4点的任务,嗯,就是在4点。
使用第二种方法,调度器会执行1点的任务,并安排下一个任务在2点。由于系统停机,2点的任务没有运行,3点的任务也没有运行。但是当系统在3:20重启时,它会发现“错过”了2点的任务,并在3:20执行它,然后再安排在4点执行。
每种方法都有优缺点。第一种方法会错过任务。第二种方法也可能错过任务,但它可以“赶上”(在一定程度上),不过也可能会在“错误的时间”运行任务(也许它应该在整点运行是有原因的)。
第二种方法的一个好处是,如果你在执行任务的结束时重新安排,就不必担心任务连锁反应的问题。
假设你有一个每分钟运行一次的任务。使用第一种方法,任务每分钟都会被触发。然而,通常情况下,如果任务在一分钟内没有完成,那么可能会有两个任务同时运行(一个在执行中,另一个刚开始)。如果这个任务设计得不能同时运行多次,这可能会造成问题。如果真的出问题,10分钟后你可能会有10个任务在争抢资源。
使用第二种方法,如果你在任务结束时重新安排,那么如果一个任务刚好超过一分钟,你就会“跳过”这一分钟,而是在下一分钟开始,而不是重复执行。所以,你可能会安排每分钟运行的任务,实际上在1:01、1:03、1:05等时间运行。
根据你的任务设计,这两种方法都可能是“好”或“坏”的。这里没有绝对的正确答案。
最后,实施第一种方法相对简单,跟实施第二种方法比起来。判断一个cron字符串(比如说)是否匹配给定时间的代码很简单,而推导一个cron字符串下一个有效时间的代码就复杂多了。我知道,我有几百行代码可以证明这一点。真不简单。