作者:shhnwangjian
APScheduler定时框架
APScheduler是一个Python定时任务框架,使用起来十分方便。提供了基于日期,固定时间间隔及crontab类型的任务,并且可以持久化任务,并以daemon方式运行应用。
github:https://github.com/agronholm/apscheduler
官网文档:https://apscheduler.readthedocs.io/en/latest/
安装
pip install apscheduler
简单示例(首先添加一个周一到周五定时执行任务)
from apscheduler.schedulers.blocking import BlockingScheduler from datetime import datetime # 输出时间 def job(): print(datetime.now().strtime("%Y-%m-%d %H:%M:%S")) # BlockingScheduler scheduler = BlockingScheduler() scheduler.add_job(job, "cron", day_of_week="1-5", hour=6, minute=30) schduler.start()
BlockingScheduler是APScheduler中的调度器,APScheduler中有两种常用的调度器,BlockingScheduler和BackgroundScheduler,当调度器是应用中唯一要运行的定时任务时,使用BlockingScheduler,如果希望调度器在后台执行,使用BackgroundScheduler。
APScheduler组成
APScheduler五个组成分别为:触发器(trigger),作业存储(job store),执行器(executor),调度器(scheduler)、任务或作业(task)。
任务(job)
描述一个任务本身。
触发器(trigger)
包含调度逻辑,每一个作业有它自己的触发器,用于决定接下来哪一个作业会运行。除了他们自己初始配置意外,触发器完全是无状态的
APScheduler 有三种内建的 trigger:
- interval: 固定时间间隔触发
- date: 特定的时间点触发
- cron: 在特定时间周期性地触发
interval 间隔调度(每隔多久执行)
weeks ( int ) – number of weeks to wait days ( int ) – number of days to wait hours ( int ) – number of hours to wait minutes ( int ) – number of minutes to wait seconds ( int ) – number of seconds to wait start_date (datetime| str ) – starting point for the interval calculation end_date (datetime| str ) – latest possible date / time to trigger on timezone (datetime.tzinfo| str ) – time zone to use for the date / time calculations |
例子:
|
#表示每隔3天17时19分07秒执行一次任务 sched.add_job(my_job, 'interval' , days = 03 , hours = 17 , minutes = 19 , seconds = 07 ) #间隔3秒钟执行一次 scheduler.add_job(job3, 'interval' , seconds = 3 ) |
date 定时调度(作业只会执行一次)
|
run_date (datetime| str ) – the date / time to run the job at - (任务开始的时间) timezone (datetime.tzinfo| str ) – time zone for run_date if it doesn’t have one already |
例子:
|
#在指定的时间,只执行一次 scheduler.add_job(tick, 'date' , run_date = '2016-02-14 15:01:05' ) # The job will be executed on November 6th, 2009 sched.add_job(my_job, 'date' , run_date = date( 2009 , 11 , 6 ), args = [ 'text' ]) # The job will be executed on November 6th, 2009 at 16:30:05 sched.add_job(my_job, 'date' , run_date = datetime( 2009 , 11 , 6 , 16 , 30 , 5 ), args = [ 'text' ]) |
cron定时调度(某一定时时刻执行)
|
( int | str ) 表示参数既可以是 int 类型,也可以是 str 类型 (datetime | str ) 表示参数既可以是datetime类型,也可以是 str 类型 year ( int | str ) – 4 - digit year - (表示四位数的年份,如 2008 年) month ( int | str ) – month ( 1 - 12 ) - (表示取值范围为 1 - 12 月) day ( int | str ) – day of the ( 1 - 31 ) - (表示取值范围为 1 - 31 日) week ( int | str ) – ISO week ( 1 - 53 ) - (格里历 2006 年 12 月 31 日可以写成 2006 年 - W52 - 7 (扩展形式)或 2006W527 (紧凑形式)) day_of_week ( int | str ) – number or name of weekday ( 0 - 6 or mon,tue,wed,thu,fri,sat,sun) - (表示一周中的第几天,既可以用 0 - 6 表示也可以用其英语缩写表示) hour ( int | str ) – hour ( 0 - 23 ) - (表示取值范围为 0 - 23 时) minute ( int | str ) – minute ( 0 - 59 ) - (表示取值范围为 0 - 59 分) second ( int | str ) – second ( 0 - 59 ) - (表示取值范围为 0 - 59 秒) start_date (datetime| str ) – earliest possible date / time to trigger on (inclusive) - (表示开始时间) end_date (datetime| str ) – latest possible date / time to trigger on (inclusive) - (表示结束时间) timezone (datetime.tzinfo| str ) – time zone to use for the date / time calculations (defaults to scheduler timezone) - (表示时区取值) |
参数的取值格式:
例子:
|
#表示2017年3月22日17时19分07秒执行该程序 sched.add_job(my_job, 'cron' , year = 2017 ,month = 03 ,day = 22 ,hour = 17 ,minute = 19 ,second = 07 ) #表示任务在6,7,8,11,12月份的第三个星期五的00:00,01:00,02:00,03:00 执行该程序 sched.add_job(my_job, 'cron' , month = '6-8,11-12' , day = '3rd fri' , hour = '0-3' ) #表示从星期一到星期五5:30(AM)直到2014-05-30 00:00:00 sched.add_job(my_job(), 'cron' , day_of_week = 'mon-fri' , hour = 5 , minute = 30 ,end_date = '2014-05-30' ) #表示每5秒执行该程序一次,相当于interval 间隔调度中seconds = 5 sched.add_job(my_job, 'cron' ,second = '*/5' ) |
作业储存(job store)
存储被调度的作业,默认的作业存储是简单地把作业保存在内存中,其他的作业存储是将作业保存在数据库中。一个作业的数据保存在持久化作业存储时被序列化,并在加载时被反序列化。调度器不能分享同一个作业存储。
APScheduler 默认使用 MemoryJobStore,可以修改使用 db 存储方案。
jobstore提供给scheduler一个序列化jobs的统一抽象,提供对scheduler中job的增删改查接口,根据存储backend的不同,分以下几种
- MemoryJobStore:没有序列化,jobs就存在内存里,增删改查也都是在内存中操作
- SQLAlchemyJobStore:所有sqlalchemy支持的数据库都可以做为backend,增删改查操作转化为对应backend的sql语句
- MongoDBJobStore:用mongodb作backend
- RedisJobStore: 用redis作backend
- ZooKeeperJobStore:用ZooKeeper做backend
执行器(executor)
处理作业的运行,他们通常通过在作业中提交制定的可调用对象到一个线程或者进城池来进行。当作业完成时,执行器将会通知调度器。
最常用的 executor 有两种:
- ProcessPoolExecutor
- ThreadPoolExecutor
调度器(scheduler)
通常在应用中只有一个调度器,应用的开发者通常不会直接处理作业存储、调度器和触发器,相反,调度器提供了处理这些的合适的接口。配置作业存储和执行器可以在调度器中完成,例如添加、修改和移除作业。
调度器(scheduler)的IO模型
scheduler由于IO模型的不同,可以有多种实现:
- BlockingScheduler:main_loop就在当前进程的主线程内运行,所以调用start函数后会阻塞当前线程。通过一个threading.Event条件变量对象完成scheduler的定时唤醒。
- BackgroundScheduler:和BlockingScheduler基本一样,除了main_loop放在了单独线程里,所以调用start后主线程不会阻塞
- AsyncIOScheduler:使用asyncio作为IO模型的scheduler,和AsyncIOExecutor配合使用,用asynio中event_loop的call_later完成定时唤醒
- GeventScheduler:和BlockingScheduler基本一样,使用gevent作为IO模型,和GeventExecutor配合使用
- QtScheduler:使用QTimer完成定时唤醒
- TornadoScheduler:使用tornado的IO模型,用ioloop.add_timeout完成定时唤醒
- TwistedScheduler:配合TwistedExecutor,用reactor.callLater完成定时唤醒
1、BlockingScheduler简单应用:
|
from apscheduler.schedulers.blocking import BlockingScheduler def my_job(): print 'hello world' scheduler = BlockingScheduler() scheduler.add_job(my_job, 'interval' , seconds = 5 ) scheduler.start() |
上面的例子表示每隔5s执行一次my_job函数,输出hello world
2、BackgroundScheduler简单应用
from apscheduler.schedulers.background import BackgroundScheduler def job3(): print (time.strftime( '%Y-%m-%d %H:%M:%S' ,time.localtime(time.time()))) print ( "I'm working job_3" ) scheduler = BackgroundScheduler() scheduler.add_job(job3, 'interval' , seconds = 3 ) #间隔3秒钟执行一次 scheduler.start() |
配置调度器
APScheduler提供了许多不同的方式来配置调度器,你可以使用一个配置字典或者作为参数关键字的方式传入。你也可以先创建调度器,再配置和添加作业,这样你可以在不同的环境中得到更大的灵活性。
下面来看一个简单的 BlockingScheduler 例子
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(job, 'interval', seconds=5) sched.start()
上述代码创建了一个 BlockingScheduler,并使用默认内存存储和默认执行器。(默认选项分别是 MemoryJobStore 和 ThreadPoolExecutor,其中线程池的最大线程数为10)。配置完成后使用 start() 方法来启动。
如果想要显式设置 job store(使用mongo存储)和 executor 可以这样写:
from datetime import datetime from pymongo import MongoClient from apscheduler.schedulers.blocking import BlockingScheduler from apscheduler.jobstores.memory import MemoryJobStore from apscheduler.jobstores.mongodb import MongoDBJobStore from apscheduler.executors.pool import ThreadPoolExecutor, ProcessPoolExecutor # MongoDB 参数 host = '127.0.0.1' port = 27017 client = MongoClient(host, port) # 输出时间 def job(): print(datetime.now().strftime("%Y-%m-%d %H:%M:%S")) # 存储方式 jobstores = { 'mongo': MongoDBJobStore(collection='job', database='test', client=client), 'default': MemoryJobStore() } executors = { 'default': ThreadPoolExecutor(10), 'processpool': ProcessPoolExecutor(3) } job_defaults = { 'coalesce': False, 'max_instances': 3 } scheduler = BlockingScheduler(jobstores=jobstores, executors=executors, job_defaults=job_defaults) scheduler.add_job(job, 'interval', seconds=5, jobstore='mongo') scheduler.start()
配置
import time import redis from pytz import utc from pymongo import MongoClient from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.jobstores.mongodb import MongoDBJobStore from apscheduler.jobstores.memory import MemoryJobStore from apscheduler.jobstores.redis import RedisJobStore from apscheduler.executors.pool import ThreadPoolExecutor, ProcessPoolExecutor from apscheduler.events import EVENT_JOB_MAX_INSTANCES, EVENT_JOB_ERROR, EVENT_JOB_MISSED client = MongoClient(host = '127.0.0.1' , port = 27017 ) pool = redis.ConnectionPool(host = '127.0.0.1' , port = 16379 ) jobstores = { 'mongo' : MongoDBJobStore(collection = 'job' , database = 'test' , client = client), 'redis' : RedisJobStore(connection_pool = pool), 'default' : MemoryJobStore(), 'default_test' : MemoryJobStore() } executors = { 'default' : ThreadPoolExecutor( 200 ), 'processpool' : ProcessPoolExecutor( 10 ) } job_defaults = { 'coalesce' : True , 'max_instances' : 1 , 'misfire_grace_time' : 60 } scheduler = BackgroundScheduler(jobstores = jobstores, executors = executors, job_defaults = job_defaults, timezone = utc) |
- coalesce:当由于某种原因导致某个job积攒了好几次没有实际运行(比如说系统挂了5分钟后恢复,有一个任务是每分钟跑一次的,按道理说这5分钟内本来是“计划”运行5次的,但实际没有执行),如果coalesce为True,下次这个job被submit给executor时,只会执行1次,也就是最后这次,如果为False,那么会执行5次(不一定,因为还有其他条件,看后面misfire_grace_time的解释)
- max_instance: 就是说同一个job同一时间最多有几个实例再跑,比如一个耗时10分钟的job,被指定每分钟运行1次,如果我们max_instance值为5,那么在第6~10分钟上,新的运行实例不会被执行,因为已经有5个实例在跑了。默认情况下同一个job,只允许一个job实例运行。这在某个job在下次运行时间到达之后仍未执行完毕时,能达到控制的目的。你也可以该变这一行为,在你调用add_job时可以传递max_instances=5来运行同时运行同一个job的5个job实例。
- misfire_grace_time:设想和上述coalesce类似的场景,如果一个job本来14:00有一次执行,但是由于某种原因没有被调度上,现在14:01了,这个14:00的运行实例被提交时,会检查它预订运行的时间和当下时间的差值(这里是1分钟),大于我们设置的30秒限制,那么这个运行实例不会被执行。
- executor的选择:线程池和进程池。默认default是线程池方式。这个数是执行任务的实际并发数,如果你设置的小了而job添加的比较多,可能出现丢失调度的情况。同时对于python多线程场景,如果是计算密集型任务,实际的并发度达不到配置的数量。所以这个数字要根据具体的要求设置。
对job的操作
添加jop
添加job有两种方式:
(1)add_job():调用函数添加,返回返回一个apscheduler.job.Job 的实例,可以用来改变或者移除 job。
from apscheduler.schedulers.blocking import BlockingScheduler from datetime import datetime # 输出时间 def job(): print(datetime.now().strtime("%Y-%m-%d %H:%M:%S")) # BlockingScheduler scheduler = BlockingScheduler() scheduler.add_job(job, "cron", day_of_week="1-5", hour=6, minute=30) schduler.start()
(2)scheduled_job():装饰器方式,只适用于应用运行期间不会改变的 job
from apscheduler.schedulers.blocking import BlockingScheduler sched = BlockingScheduler() # 装饰器 @sched.scheduled_job('interval', id='my_job_id', seconds=5) def job_function(): print("Hello World") # 开始 sched.start()
移除job
移除job有两种方法:
1,remove_job():apscheduler实例调用remove_job使用jobID移除。
# id scheduler.add_job(myfunc, 'interval', minutes=2, id='my_job_id') scheduler.remove_job('my_job_id')
2,job.remove():apscheduler.job.Job 实例调用自身函数属性。
job = scheduler.add_job(myfunc, 'interval', minutes=2) job.remove()
暂停jod
apscheduler.job.Job.pause()
apscheduler.schedulers.base.BaseScheduler.pause_job()
apscheduler.job.Job 是 add_job() 返回的实例
获取job列表
获得可调度 job 列表,可以使用get_jobs() 来完成,它会返回所有的 job 实例。
也可以使用print_jobs() 来输出所有格式化的 job 列表。
修改job
除了 jobID 之外 job 的所有属性都可以修改,使用 apscheduler.job.Job.modify() 或者 modify_job() 修改一个 job 的属性
job.modify(max_instances=6, name='Alternate name') modify_job('my_job_id', trigger='cron', minute='*/5')
关闭job
默认情况下调度器会等待所有的 job 完成后,关闭所有的调度器和作业存储。将 wait 选项设置为 False 可以立即关闭。
scheduler.shutdown()
scheduler.shutdown(wait=False)