APScheduler(定时任务二配置调度器)
一. 配置调度器
APScheduler 有多种不同的配置方法,你可以选择直接传字典或传参的方式创建调度器;
也可以先实例一个调度器对象,再添加配置信息。灵活的配置方式可以满足各种应用场景的需要。
整套的配置选项可以参考API文档BaseScheduler
类。
一些调度器子类可能有它们自己特有的配置选项,以及独立的任务储存器和执行器也可能有自己特有的配置选项,可以查阅API文档了解。
1.1 举一个例子,创建一个使用默认任务储存器和执行器的BackgroundScheduler
:
from apscheduler.schedulers.background import BackgroundScheduler scheduler = BackgroundScheduler() # 因为是非阻塞的后台调度器,所以程序会继续向下执行
这样就可以创建了一个后台调度器。这个调度器有一个名称为default
的MemoryJobStore
(内存任务储存器)和一个名称是default
且最大线程是10的ThreadPoolExecutor
(线程池执行器)。
假如你现在有这样的需求,两个任务储存器分别搭配两个执行器;同时,还要修改任务的默认参数;最后还要改时区。可以参考下面例子,它们是完全等价的。
- 名称为“mongo”的
MongoDBJobStore
- 名称为“default”的
SQLAlchemyJobStore
- 名称为“default”的
ThreadPoolExecutor
,最大线程20个 - 名称“processpool”的
ProcessPoolExecutor
,最大进程5个 - UTC时间作为调度器的时区
- 默认为新任务关闭合并模式()
- 设置新任务的默认最大实例数为3
#-*- coding:utf-8 -*- from pytz import utc from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.jobstores.mongodb import MongoDBJobStore from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore from apscheduler.executors.pool import ThreadPoolExecutor, ProcessPoolExecutor jobstores = { 'mongo': MongoDBJobStore(), #调度器名称为mongo的MemoryJobStore(内存任务储存器) 'default': SQLAlchemyJobStore(url='sqlite:///jobs.sqlite')#调度器名称为“default”的SQLAlchemyJobStore } executors = { 'default': ThreadPoolExecutor(20),#名称为“default ”的ThreadPoolExecutor,最大线程20个 'processpool': ProcessPoolExecutor(5)#名称“processpool”的ProcessPoolExecutor,最大进程5个 } job_defaults = { 'coalesce': False, #默认为新任务关闭合并模式() 'max_instances': 3#设置新任务的默认最大实例数为3 } scheduler = BackgroundScheduler(jobstores=jobstores, executors=executors, job_defaults=job_defaults, timezone=utc)
方法二:
#-*- coding:utf-8 -*- from apscheduler.schedulers.background import BackgroundScheduler # The "apscheduler." prefix is hard coded scheduler = BackgroundScheduler({ 'apscheduler.jobstores.mongo': { 'type': 'mongodb' }, 'apscheduler.jobstores.default': { 'type': 'sqlalchemy', 'url': 'sqlite:///jobs.sqlite' }, 'apscheduler.executors.default': { 'class': 'apscheduler.executors.pool:ThreadPoolExecutor', 'max_workers': '20' }, 'apscheduler.executors.processpool': { 'type': 'processpool', 'max_workers': '5' }, 'apscheduler.job_defaults.coalesce': 'false', 'apscheduler.job_defaults.max_instances': '3', 'apscheduler.timezone': 'UTC', })
方法三:
#-*- coding:utf-8 -*- from pytz import utc from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore from apscheduler.executors.pool import ProcessPoolExecutor jobstores = { 'mongo': {'type': 'mongodb'}, 'default': SQLAlchemyJobStore(url='sqlite:///jobs.sqlite') } executors = { 'default': {'type': 'threadpool', 'max_workers': 20}, 'processpool': ProcessPoolExecutor(max_workers=5) } job_defaults = { 'coalesce': False, 'max_instances': 3 } scheduler = BackgroundScheduler() # ..这里可以添加任务 scheduler.configure(jobstores=jobstores, executors=executors, job_defaults=job_defaults, timezone=utc)
1.2 启动调度器
启动调度器是只需调用start()
即可。除了BlockingScheduler
,非阻塞调度器都会立即返回,可以继续运行之后的代码,比如添加任务等。
对于BlockingScheduler
,程序则会阻塞在start()
位置,所以,要运行的代码必须写在start()
之前。
注!调度器启动后,就不能修改配置了。
1.3 添加任务
添加任务的方法有两种:
- 通过调用
add_job()
- 通过装饰器
scheduled_job()
第一种方法是最常用的;
第二种方法是最方便的,但缺点就是运行时,不能修改任务。
第一种add_job()
方法会返回一个apscheduler.job.Job
实例,这样就可以在运行时,修改或删除任务。在任何时候你都能配置任务。但是如果调度器还没有启动,此时添加任务,那么任务就处于一个暂存的状态。只有当调度器启动时,才会开始计算下次运行时间。
还有一点要注意,如果你的执行器或任务储存器是会序列化任务的,那么这些任务就必须符合:
- 回调函数必须全局可用
- 回调函数参数必须也是可以被序列化的
内置任务储存器中,只有MemoryJobStore
不会序列化任务;内置执行器中,只有ProcessPoolExecutor
会序列化任务。
重要提醒!
如果在程序初始化时,是从数据库读取任务的,那么必须为每个任务定义一个明确的ID,并且使用replace_existing=True
,否则每次重启程序,你都会得到一份新的任务拷贝,也就意味着任务的状态不会保存。
建议
如果想要立刻运行任务,可以在添加任务时省略trigger
参数
1.4 移除任务
如果想从调度器移除一个任务,那么你就要从相应的任务储存器中移除它,这样才算移除了。有两种方式:
- 调用
remove_job()
,参数为:任务ID,任务储存器名称 - 在通过
add_job()
创建的任务实例上调用remove()
方法
第二种方式更方便,但前提必须在创建任务实例时,实例被保存在变量中。对于通过scheduled_job()
创建的任务,只能选择第一种方式。
当任务调度结束时(比如,某个任务的触发器不再产生下次运行的时间),任务就会自动移除。
job = scheduler.add_job(myfunc, 'interval', minutes=2) job.remove()
同样,通过任务的具体ID:
scheduler.add_job(myfunc, 'interval', minutes=2, id='my_job_id') scheduler.remove_job('my_job_id')
1.5 暂停和恢复任务
通过任务实例或调度器,就能暂停和恢复任务。如果一个任务被暂停了,那么该任务的下一次运行时间就会被移除。在恢复任务前,运行次数计数也不会被统计。
暂停任务,有以下两个方法:
apscheduler.job.Job.pause()
apscheduler.schedulers.base.BaseScheduler.pause_job()
恢复任务,
apscheduler.job.Job.resume()
apscheduler.schedulers.base.BaseScheduler.resume_job()
1.6 获取任务列表
通过get_jobs()
就可以获得一个可修改的任务列表。get_jobs()
第二个参数可以指定任务储存器名称,那么就会获得对应任务储存器的任务列表。
print_jobs()
可以快速打印格式化的任务列表,包含触发器,下次运行时间等信息。
1.7 修改任务
通过apscheduler.job.Job.modify()
或modify_job()
,你可以修改任务当中除了id
的任何属性。
比如:
job.modify(max_instances=6, name='Alternate name')
如果想要重新调度任务(就是改变触发器),你能通过apscheduler.job.Job.reschedule()
或reschedule_job()
来实现。这些方法会重新创建触发器,并重新计算下次运行时间。
比如:
scheduler.reschedule_job('my_job_id', trigger='cron', minute='*/5')
1.8 关闭调度器
关闭方法如下:
scheduler.shutdown()
默认情况下,调度器会先把正在执行的任务处理完,再关闭任务储存器和执行器。但是,如果你就直接关闭,你可以添加参数:
scheduler.shutdown(wait=False)
上述方法不管有没有任务在执行,会强制关闭调度器。
1.9 暂停、恢复任务进程
调度器可以暂停正在执行的任务:
scheduler.pause()
也可以恢复任务:
scheduler.resume()
同时,也可以在调度器启动时,默认所有任务设为暂停状态。
scheduler.start(paused=True)
2.0 限制任务执行的实例并行数
默认情况下,在同一时间,一个任务只允许一个执行中的实例在运行。
比如说,一个任务是每5秒执行一次,但是这个任务在第一次执行的时候花了6秒,也就是说前一次任务还没执行完,
后一次任务又触发了,由于默认一次只允许一个实例执行,所以第二次就丢失了。为了杜绝这种情况,可以在添加任务时,
设置max_instances
参数,为指定任务设置最大实例并行数。
2.1 丢失任务的执行与合并
有时,任务会由于一些问题没有被执行。
最常见的情况就是,在数据库里的任务到了该执行的时间,但调度器被关闭了,那么这个任务就成了“哑弹任务”。
错过执行时间后,调度器才打开了。这时,调度器会检查每个任务的misfire_grace_time
参数int
值,即哑弹上限,来确定是否还执行哑弹任务(这个参数可以全局设定的或者是为每个任务单独设定)。
此时,一个哑弹任务,就可能会被连续执行多次。
但这就可能导致一个问题,有些哑弹任务实际上并不需要被执行多次。coalescing
合并参数就能把一个多次的哑弹任务揉成一个一次的哑弹任务。
也就是说,coalescing
为True
能把多个排队执行的同一个哑弹任务,变成一个,而不会触发哑弹事件。
注!如果是由于线程池/进程池满了导致的任务延迟,执行器就会跳过执行。要避免这个问题,可以添加进程或线程数来实现或把
misfire_grace_time
值调高。
2.2 调度器事件
调度器允许添加事件侦听器。部分事件会有特有的信息,比如当前运行次数等。add_listener(callback,mask)
中,第一个参数是回调对象,mask
是指定侦听事件类型,mask
参数也可以是逻辑组合。回调对象会有一个参数就是触发的事件。
具体可以查看文档中events
模块,里面有关于事件类型以及事件参数的详细说明。
def my_listener(event): if event.exception: print('The job crashed :(') else: print('The job worked :)') # 当任务执行完或任务出错时,调用my_listener scheduler.add_listener(my_listener, EVENT_JOB_EXECUTED | EVENT_JOB_ERROR)
事件类型
Constant | Description | Event class |
---|---|---|
EVENT_SCHEDULER_STARTED |
计划程序已启动 |
SchedulerEvent |
EVENT_SCHEDULER_SHUTDOWN | 调度程序已关闭 | SchedulerEvent |
EVENT_SCHEDULER_PAUSED | 计划程序中的作业处理已暂停 | SchedulerEvent |
EVENT_SCHEDULER_RESUMED | 计划程序中的作业处理已恢复 | SchedulerEvent |
EVENT_EXECUTOR_ADDED | 已将执行器添加到计划程序中 | SchedulerEvent |
EVENT_EXECUTOR_REMOVED | 一个执行器被移到调度程序中 | SchedulerEvent |
EVENT_JOBSTORE_ADDED | 已将作业存储添加到计划程序 | SchedulerEvent |
EVENT_JOBSTORE_REMOVED |
已从计划程序中删除作业存储 |
SchedulerEvent |
EVENT_ALL_JOBS_REMOVED | 所有作业都已从所有作业存储或一个特定作业存储中删除 | SchedulerEvent |
EVENT_JOB_ADDED |
作业已添加到作业存储中 |
JobEvent |
EVENT_JOB_REMOVED | 已从作业存储中删除作业 | JobEvent |
EVENT_JOB_MODIFIED | 已从计划程序外部修改作业 | JobEvent |
EVENT_JOB_SUBMITTED |
作业已提交给其执行者以运行 |
JobSubmissionEvent |
EVENT_JOB_MAX_INSTANCES |
提交给其执行者的作业未被执行者接受,因为该作业已达到其最大并发执行实例数 |
JobSubmissionEvent |
EVENT_JOB_EXECUTED |
作业已成功执行 |
JobExecutionEvent |
EVENT_JOB_ERROR |
作业在执行期间引发异常 |
JobExecutionEvent |
EVENT_JOB_MISSED |
错失了一份工作 |
JobExecutionEvent |
EVENT_ALL |
包含所有事件类型的全部捕获掩码 |
N/A |
2.3 异常捕获
通过logging模块,可以添加apscheduler
日志至DEBUG
级别,这样就能捕获异常信息。
关于logging
初始化的方式如下:
import logging logging.basicConfig() logging.getLogger('apscheduler').setLevel(logging.DEBUG)
日志会提供很多调度器的内部运行信息。
文章参考链接:https://apscheduler.readthedocs.io/en/latest/index.html