目录
前言:
因为自用的定时任务出现一些问题,打算周末抽个时间将APScheduler模块弄明白,做个记录,避免再次出现问题。
原因
出现这两个错误的原因实在定时在调度的时候出现的问题导致的。先给出模块的默认几个参数:
executors = {
# 执行器的线程与进程数
'default': ThreadPoolExecutor(10),
'processpool': ProcessPoolExecutor(10)
}
job_defaults = {
# 最近多久时间内允许存在的任务数
'misfire_grace_time': 1,
# 该定时任务允许最大的实例个数
'max_instances': 1,
# 是否运行一次最新的任务,当多个任务堆积时
'coalesce': True,
# 默认值的设置很科学啊
}
scheduler = BackgroundScheduler(executors=executors, job_defaults=job_defaults)
Execution of job maximum number of running instances原因
报错信息很明显,达到了运行实例的最大值。由于默认值max_instances的设置,一个时刻只应该运行一个实例,想一下它的合理性,常规情况下定时任务就是应该在下次调度时运行完成。造成这个问题就是设置的定时任务不是很合理。
Run time of job was missed原因
同理,当检测到当前时间减定时执行时的时间大于misfire_grace_time设置的时间时,该任务就会丢失。
临时解决方案(看起来很美)
错误1:
增大max_instances,增大ThreadPoolExecutor:仅仅增大实例个数不够的,没有线程运行程序,积攒到一定时候还是会报错的。再过分一点就是增大misfire_grace_time,让任务堆积着。
错误2:
在1的基础上,修改coalesce为False,这样任务就会一直在调度器堆积着。
看到这,报错已经基本解决了。要是不想知道原理的就可以关闭窗口了。
下面就是介绍一些定时调度原理和彻底解决的问题的方法。
APScheduler介绍
首先介绍4个重要概念:
- 触发器(triggers)
- 作业存储器(job stores)
- 执行器(executors)
- 调度器(schedulers)
触发器作用是用来决定下次执行任务的时间。触发器在初始配置之后,是一种无状态的(无状态指的什么)。
作业存储器用来存储定时任务,可以存储在内存或者数据库中。作业存储器不能在调度器之间共享(不理解)。
执行器是将具体的任务交给谁去执行,某个进程或线程。当执行完后,会通知调度器。
调度器为开发人员直接设置,用来调用其它3个工具完成定时任务的执行。
一定要了解这四个概念,至少整个调度任务应用层面的东西都可以通过他们之间的关系解决。
四者之间的关系图,辅助记忆:
APScheduler例子
首先介绍例子运行的环境:
模块 | 版本 |
---|---|
Ubuntu | 18.04 |
APScheduler | 3.6.3 |
Django | 3.1.3 |
偷懒程序直接运行在Django框架,可以脚本直接运行。
基础参数配置:
将线程和进程都调为1,容易出现报错,
参数 | 数值 |
---|---|
ThreadPoolExecutor | 1 |
ProcessPoolExecutor | 1 |
misfire_grace_time | 2(用于区分比较大小,默认为1) |
max_instances | 1 |
coalesce | True |
两个定时任务:red在打印当前时间后sleep10秒,green在打印当前时间后sleep4秒。
定时任务每3秒调度一次。
任务正常执行如下图所示
补充说明:在程序运行之初,仅仅调度器开始工作,job并没有运行。图中的00:00为定时任务运行起始点,并不是程序运行起始点。
在只有一个线程的情况下
默认参数
参数 | 数值 |
---|---|
misfire_grace_time | 1 |
max_instances | 1 |
coalesce | True |
运行结果如下:
因为只有一个线程,所以同一时刻只有一个job可以被调度运行,job stores是以队列的方式进行存储。
- 00:00
调度器调度执行器开始工作,生成两个job,apple_red和apple_green,由于只有一个线程,只能够执行red工作, - 00:03
触发器触发判断条件,apple_red有1个job正在执行so大于max_instances,所以跳过执行,apple_green有一个job等待执行,所以跳过。00:06,00:09同理 - 00:10
这时apple_red运行完,通知传递给调度器后调度器调度apple_green实例,结果,判断任务持续时间10大于misfire_grace_time,就取消了该job的运行,造成missed。 - 00:12
情况等同于00:00。
调整参数
misfire_grace_time:11,max_instances: 1
参数 | 数值 |
---|---|
misfire_grace_time | 11 |
max_instances | 1 |
coalesce | True |
运行结果如下:
运行实例如下:有颜色代表线程运行,无颜色代表队列状
因为只有一个线程,所以同一时刻只有一个job可以被调度运行。
- 00:00
调度器调度执行器开始工作,生成两个job,apple_red和apple_green,由于只有一个线程,只能够执行red工作, - 00:03
触发器触发判断条件,apple_red有1个job正在执行so大于max_instances,所以跳过执行,apple_green有一个job等待执行,所以跳过。00:06,00:09同理 - 00:10
这时apple_red运行完,通知传递给调度器后调度器调度apple_green实例,结果,判断任务持续时间10小于misfire_grace_time11,green运行,job stores为空。 - 00:12
情况与00:03相反。
参数调整
misfire_grace_time:1,max_instances: 2
参数 | 数值 |
---|---|
misfire_grace_time | 1 |
max_instances | 2 |
coalesce | True |
运行情况:
- 00:00
同前一种情况,调度器调度执行器开始工作,生成两个job,apple_red和apple_green,由于只有一个线程,只能够执行apple_red工作 - 00:03
触发器触发判断条件,生成两个job,red和green,apple_red。 - 00:06
apple_red有1个job正在执行和等待的3秒时候生成的job,大于max_instances,所以跳过执行,apple_green有2个job等待执行,所以跳过。00:09同理 - 00:10
这时apple_red运行完,通知传递给调度器后,调度器有3job等待,结果,判断任务持续时间10大于misfire_grace_time,就取消了该job的运行,造成missed。 - 00:12
同00:00情况。
参数调整
misfire_grace_time:11,max_instances: 2
参数 | 数值 |
---|---|
misfire_grace_time | 11 |
max_instances | 2 |
coalesce | True |
这种情况运行情况理解了一样的,同前一样,只放结果了
运行结果情况:
两个线程的情况下
参数
参数 | 数值 |
---|---|
misfire_grace_time | 11 |
max_instances | 2 |
coalesce | True |
执行结果如下:
详细运行过程如下:
- 00:00
同前一种情况,调度器调度执行器开始工作,生成两个job,apple_red和apple_green,两个线程分别执行。 - 00:03
触发器触发判断条件,生成两个job,red和green,入队。 - 00:04
apple_green完成,执行器返回信号给调度器,调度器从队列中拿出red交给executors执行, - 00:06-00:09
red达到2个,入队一个green。 - 00:10
apple_red完成,执行器返回信号给调度器,调度器从队列中拿出green交给executors执行 - 00:12
red入队。
总结:
花费了将近一天的时间总算是写完了,文章中有些表述不是很恰当的地方,待完成源码的查看后进行修改,定时任务看似很简单,里面涉及到的细小知识点还是很多的。
参考:
1、官方文档
2、https://cloud.tencent.com/developer/article/1662162
3、http://www.vuln.cn/9121