APScheduler(advanceded python scheduler)基于Quartz的一个Python定时任务框架,实现了Quartz的所有功能,使用起来十分方便。提供了基于日期、固定时间间隔以及crontab类型的任务,并且可以持久化任务。基于这些功能,我们可以很方便的实现一个Python定时任务系统。
它有以下三个特点:
APScheduler有四种组成部分:
Job作为APScheduler最小执行单位。创建Job时指定执行的函数,函数中所需参数,Job执行时的一些设置信息。
Trigger绑定到Job,在scheduler调度筛选Job时,根据触发器的规则计算出Job的触发时间,然后与当前时间比较确定此Job是否会被执行,总之就是根据trigger规则计算出下一个执行时间。
目前APScheduler支持触发器:
date定时,作业只执行一次。
如下:
sched.add_job(my_job, 'date', run_date=date(2009, 11, 6), args=['text'])
sched.add_job(my_job, 'date', run_date=datetime(2019, 7, 6, 16, 30, 5), args=['text'])
interval间隔调度
如下:
sched.add_job(job_function, 'interval', hours=2)
cron调度
# 6-8,11-12月第三个周五 00:00, 01:00, 02:00, 03:00运行
sched.add_job(job_function, 'cron', month='6-8,11-12', day='3rd fri', hour='0-3')
# 每周一到周五运行 直到2024-05-30 00:00:00
sched.add_job(job_function, 'cron', day_of_week='mon-fri', hour=5, minute=30, end_date='2024-05-30'
Executor在scheduler中初始化,另外也可通过scheduler的add_executor动态添加Executor。每个executor都会绑定一个alias,这个作为唯一标识绑定到Job,在实际执行时会根据Job绑定的executor找到实际的执行器对象,然后根据执行器对象执行Job。
Executor的种类会根据不同的调度来选择,如果选择AsyncIO作为调度的库,那么选择AsyncIOExecutor,如果选择tornado作为调度的库,选择TornadoExecutor,如果选择启动进程作为调度,选择ThreadPoolExecutor或者ProcessPoolExecutor都可以。
Executor的选择需要根据实际的scheduler来选择不同的执行器。目前APScheduler支持的Executor:
Jobstore在scheduler中初始化,另外也可通过scheduler的add_jobstore动态添加Jobstore。每个jobstore都会绑定一个alias,scheduler在Add Job时,根据指定的jobstore在scheduler中找到相应的jobstore,并将job添加到jobstore中。作业存储器决定任务的保存方式, 默认存储在内存中(MemoryJobStore),重启后就没有了。APScheduler支持的任务存储器有:
不同的任务存储器可以在调度器的配置中进行配置(见调度器)。
vent是APScheduler在进行某些操作时触发相应的事件,用户可以自定义一些函数来监听这些事件,当触发某些Event时,做一些具体的操作。常见的比如。Job执行异常事件 EVENT_JOB_ERROR。Job执行时间错过事件 EVENT_JOB_MISSED。
目前APScheduler定义的Event:
Listener表示用户自定义监听的一些Event,比如当Job触发了EVENT_JOB_MISSED事件时可以根据需求做一些其他处理。
Scheduler是APScheduler的核心,所有相关组件通过其定义。scheduler启动之后,将开始按照配置的任务进行调度。除了依据所有定义Job的trigger生成的将要调度时间唤醒调度之外。当发生Job信息变更时也会触发调度。
APScheduler支持的调度器方式如下,比较常用的为BlockingScheduler
和BackgroundScheduler
:
Scheduler调度流程:
一个简单的示例如下:
from apscheduler.schedulers.blocking import BlockingScheduler
from datetime import datetime
# 输出时间
def job():
print(datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
# 创建BlockingScheduler
sched = BlockingScheduler()
# 添加任务
sched.add_job(my_job, 'interval', seconds=5, id='my_job_id')
# 启动任务
sched.start()
主要步骤如下:
sched = BlockingScheduler()
第一种方法是最常用的,第二种方法通过声明 job 而不修改应用程序运行时是最为方便的;add_job() 方法返回一个 apscheduler.job.Job 实例,我们可以用它来在之后修改或移除 job;我们可以随时调度 scheduler 里的 job,如果添加 job 时,scheduler 尚未运行,job 会被临时地进行排列,直到 scheduler 启动之后,它的首次运行时间才会被确切地计算出来。
注意:
① 如果你调度的 job 在一个持久化的 job store 里,当你初始化你的应用程序时,你 必须 为 job 定义一个显式的 ID 并使用 replace_existing=True ,否则每次你的应用程序重启时你都会得到那个 job 的一个新副本
② 如果想马上运行 job ,请在添加 job 时省略 trigger 参数
③ 如果我们的执行器或任务储存器是会序列化任务的,那么这些任务就必须符合:1-回调函数必须全局可用;2-回调函数参数必须也是可以被序列化的
当从 scheduler 中移除一个 job 时,它会从关联的 job store 中被移除,不再被执行;有两种途径可以移除 job:
① 通过 job 的 ID 以及 job store 的别名来调用 remove_job() 方法
② 对你在 add_job() 中得到的 job 实例调用 remove() 方法
后者看起来更方便,实际上它要求你必须将调用 add_job() 得到的 Job 实例存储在某个地方;而对于通过 scheduled_job() 装饰器来调度 job 的就只能使用第一种方法;
如果一个 job 完成了调度(例如它的触发器不会再被触发),它会自动被移除。
# 移除job任务
myjob.remove()
bgsched.remove_job(job_id='00001') # jobstore=None
# 暂停job
myjob.pause()
# 恢复job
myjob.resume()
通过get_jobs方法获取调度列表:bgsched.get_jobs()。
# 修改job相关信息,id不能被修改
myjob.modify(name='modity_name')
默认情况下,scheduler 会终止其 job store 以及 executor,然后等待所有目前执行的 job 完成后自行终止,如果不想等待可以设置wait为False。
# 终止调度
bgsched.shutdown(wait=False)
bgsched.start()
注意:有些timezone时区可能会有夏令时的问题;这个可能导致令时切换时,任务不执行或任务执行两次;避免这个问题,可以使用UTC时间,或提前预知并规划好执行的问题。
from pytz import utc
sched = BlockingScheduler(timezone=utc)
参考:
https://blog.51cto.com/u_16147578/6396384
https://zhuanlan.zhihu.com/p/491679794