目录
时间常量
先定义下,下文可能会用到的时间常量
second = 1
minute = second * 60
hour = minute * 60
day = hour * 24
month = day * 30
year = month * 12
while实现任务调度
def task():
print("读取数据...")
time.sleep(1)
print("导出数据...")
print("file stream...")
def loop(delay):
while True:
task()
time.sleep(delay)
if __name__ == '__main__':
loop(second)
实现最简陋,最不推荐, time.sleep(delay)会阻塞主线程,导致整个程序在等待任务执行完成时无法响应其他事件。
threading.Timer实现
- threading 模块中的 Timer 是一个非阻塞函数,比 sleep 稍好一点。
- timer最基本理解就是定时器,我们可以启动多个定时任务,这些定时器任务是异步执行,所以不存在等待顺序执行问题。
from threading import Timer
def task():
print("读取数据...")
time.sleep(1)
print("导出数据...")
print("file stream...")
def loop(delay):
task()
t = Timer(interval=delay, function=loop,args=(delay,))
t.start()
if __name__ == '__main__':
loop(second)
sched实现
sched模块实现了一个通用事件调度器,在调度器类使用一个延迟函数等待特定的时间,执行任务。同时支持多线程应用程序,在每个任务执行后会立刻调用延时函数,以确保其他线程也能执行。
import sched
def task():
print("读取数据...")
time.sleep(1)
print("导出数据...")
print("file stream...")
def loop():
task()
s.enter(delay=second, priority=1, action=loop)
if __name__ == '__main__':
s = sched.scheduler(timefunc=time.time, delayfunc=time.sleep)
s.enter(delay=second, priority=1, action=loop)
s.run()
scheduler对象主要方法:
- enter(delay, priority, action, argument),安排一个事件来延迟delay个时间单位。
- cancel(event):从队列中删除事件。如果事件不是当前队列中的事件,则该方法将抛出一个ValueError。
- run():运行所有预定的事件。这个函数将等待(使用传递给构造函数的delayfunc()函数),然后执行事件,直到不再有预定的事件。
schedule实现(推荐)
pip install schedule
schedule库是一个轻量级的定时任务方案。优势是使用简单,可以直接上手,基本不需要做什么配置。缺点是,因为比较轻量,导致他的功能比较局限,比如无法动态的添加任务,无法将任务的结果进行持久化等。
import schedule
from datetime import datetime, timedelta, time
def job():
print('working...')
schedule.every().second.until('23:59').do(job) # 今天23:59停止
schedule.every().second.until('2030-01-01 18:30').do(job) # 2030-01-01 18:30停止
schedule.every().second.until(timedelta(hours=8)).do(job) # 8小时后停止
schedule.every().second.until(time(23, 59, 59)).do(job) # 今天23:59:59停止
schedule.every().second.until(datetime(2030, 1, 1, 18, 30, 0)).do(job) # 2030-01-01 18:30停止
while True:
schedule.run_pending()
Celery实现
- 注意:不要高射炮打蚊子!!schedule就挺好。
- 实现:参考以往博客:celery。
Celery在异步任务和定时任务两个方面可以说是非常出名。他性能稳定,功能强大,在中大型项目中扮演着重要的角色。但是 Celery也无法动态的添加任务( Django中有相应的插件可以实现动态任务),而且对于之前没有使用过 Celery的项目,单独因为定时任务而搭建一套 Celery系统显得会有点大材小用的感觉,因为搭建 Celery还得配一个 Broker, Broker一般是 Redis或者 RabbitMQ,所以比较重量级,也比较浪费资源。
APScheduler实现
安装:pip install apscheduler
APScheduler不仅使用简单,功能还比较强大,比如支持动态添加和删除任务,支持持久化,持久化的方式还有很多选择,比如内存、 MongoDB、 SQLAlchemy、 Redis等,也针对一些常用的框架做了接口,比如有asyncio调度器、 gevent调度器等。
APScheduler由四部分组成
- 触发器(trigger) 包含调度逻辑,每一个作业有它自己的触发器,用于决定接下来哪一个作业会运行。除了他们自己初始配置以外,触发器完全是无状态的。
- 作业存储(job store) 存储被调度的作业,默认的作业存储是简单地把作业保存在内存中,其他的作业存储是将作业保存在数据库中。一个作业的数据讲在保存在持久化作业存储时被序列化,并在加载时被反序列化。调度器不能分享同一个作业存储。
- 执行器(executor) 处理作业的运行,他们通常通过在作业中提交制定的可调用对象到一个线程或者进程池来进行。当作业完成时,执行器将会通知调度器。
- 调度器(scheduler) 是其他的组成部分。你通常在应用只有一个调度器,应用的开发者通常不会直接处理作业存储、调度器和触发器,相反,调度器提供了处理这些的合适的接口。配置作业存储和执行器可以在调度器中完成,例如添加、修改和移除作业。通过配置executor、jobstore、trigger,使用线程池(ThreadPoolExecutor默认值20)或进程池(ProcessPoolExecutor 默认值5)并且默认最多3个(max_instances)任务实例同时运行,实现对job的增删改查等调度控制
import time
from apscheduler.schedulers.blocking import BlockingScheduler
def task():
print("读取数据...")
time.sleep(1)
print("导出数据...")
print("file stream...")
def loop():
scheduler = BlockingScheduler()
scheduler.add_job(func=task, trigger="interval", seconds=second * 10)
scheduler.start()
if __name__ == '__main__':
loop()
Job 作业
Job作为APScheduler最小执行单位。创建Job时指定执行的函数,函数中所需参数,Job执行时的一些设置信息。
def add_job(self, func, trigger=None, args=None, kwargs=None, id=None, name=None,
misfire_grace_time=undefined, coalesce=undefined, max_instances=undefined,
next_run_time=undefined, jobstore='default', executor='default',
replace_existing=False, **trigger_args):
构建说明:
- id:指定作业的唯一ID
- name:指定作业的名字
- trigger:apscheduler定义的触发器,用于确定Job的执行时间,根据设置 的trigger规则,计算得到下次执行此job的时间, 满足时将会执行
- executor:apscheduler定义的执行器,job创建时设置执行器的名字,根据字符串到scheduler获取执行器,执行job指定的函数
- max_instances:执行此job的最大实例数,executor执行job时,根据job的id来计算执行次数,根据设置的最大实例数来确定是否可执行
- next_run_time:Job下次的执行时间,创建Job时可以指定一个时间[datetime],不指定的话则默认根据trigger获取触发时间
- misfire_grace_time:Job的延迟执行时间,例如Job的计划执行时间是21:00:00,但因服务重启或其他原因导致21:00:31才执行,如果设置此key为40,则该job会继续执行,否则将会丢弃此job
- coalesce:Job是否合并执行,是一个bool值。例如scheduler停止20s后重启启动,而job的触发器设置为5s执行一次,因此此job错过了4个执行时间,如果设置为是,则会合并到一次执行,否则会逐个执行
- func:Job执行的函数
- args:Job执行函数需要的位置参数
- kwargs:Job执行函数需要的关键字参数
Trigger 触发器
Trigger绑定到Job,在scheduler调度筛选Job时,根据触发器的规则计算出Job的触发时间,然后与当前时间比较确定此Job是否会被执行,总之就是根据trigger规则计算出下一个执行时间。
目前APScheduler支持触发器:
- 指定时间的DateTrigger
- 指定间隔时间的IntervalTrigger
- 像Linux的crontab一样的CronTrigger。
DateTrigger
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'])
IntervalTrigger
interval间隔调度
- weeks (int) – 间隔几周
- days (int) – 间隔几天
- hours (int) – 间隔几小时
- minutes (int) – 间隔几分钟
- seconds (int) – 间隔多少秒
- start_date (datetime|str) – 开始日期
- end_date (datetime|str) – 结束日期
- timezone (datetime.tzinfo|str) – 时区
sched.add_job(job_function, 'interval', hours=2)
CronTrigger
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) -(表示时区取值)
# 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 执行器
Executor在scheduler中初始化,另外也可通过scheduler的add_executor动态添加Executor。每个executor都会绑定一个alias,这个作为唯一标识绑定到Job,在实际执行时会根据Job绑定的executor找到实际的执行器对象,然后根据执行器对象执行Job。
Executor的种类会根据不同的调度来选择,如果选择AsyncIO作为调度的库,那么选择AsyncIOExecutor,如果选择tornado作为调度的库,选择TornadoExecutor,如果选择启动进程作为调度,选择ThreadPoolExecutor或者ProcessPoolExecutor都可以。
Executor的选择需要根据实际的scheduler来选择不同的执行器。目前APScheduler支持的Executor:
- executors.asyncio:同步io,阻塞
- executors.gevent:io多路复用,非阻塞
- executors.pool: 线程ThreadPoolExecutor和进程ProcessPoolExecutor
- executors.twisted:基于事件驱动
- ThreadPoolExecutor:线程池执行器。
- ProcessPoolExecutor:进程池执行器。
- GeventExecutor: Gevent程序执行器。
- TornadoExecutor: Tornado程序执行器。
- TwistedExecutor: Twisted程序执行器。
- AsyncIOExecutor: asyncio程序执行器。
Jobstore 作业存储
Jobstore在scheduler中初始化,另外也可通过scheduler的add_jobstore动态添加Jobstore。每个jobstore都会绑定一个alias,scheduler在Add Job时,根据指定的jobstore在scheduler中找到相应的jobstore,并将job添加到jobstore中。作业存储器决定任务的保存方式, 默认存储在内存中(MemoryJobStore),重启后就没有了。APScheduler支持的任务存储器有:
- jobstores.memory:内存
- jobstores.mongodb:存储在mongodb
- jobstores.redis:存储在redis
- jobstores.rethinkdb:存储在rethinkdb
- jobstores.sqlalchemy:支持sqlalchemy的数据库如mysql,sqlite等
- jobstores.zookeeper:zookeeper
不同的任务存储器可以在调度器的配置中进行配置(见调度器)
- MemoryJobStore:没有序列化,任务存储在内存中,增删改查都是在内存中完成。
- SQLAlchemyJobStore:使用 SQLAlchemy这个 ORM框架作为存储方式。
- MongoDBJobStore:使用 mongodb作为存储器。
- RedisJobStore:使用 redis作为存储器。
Event 事件
Event是APScheduler在进行某些操作时触发相应的事件,用户可以自定义一些函数来监听这些事件,当触发某些Event时,做一些具体的操作。常见的比如。Job执行异常事件 EVENT_JOB_ERROR。Job执行时间错过事件 EVENT_JOB_MISSED。
目前APScheduler定义的Event:
EVENT_SCHEDULER_STARTED
EVENT_SCHEDULER_START
EVENT_SCHEDULER_SHUTDOWN
EVENT_SCHEDULER_PAUSED
EVENT_SCHEDULER_RESUMED
EVENT_EXECUTOR_ADDED
EVENT_EXECUTOR_REMOVED
EVENT_JOBSTORE_ADDED
EVENT_JOBSTORE_REMOVED
EVENT_ALL_JOBS_REMOVED
EVENT_JOB_ADDED
EVENT_JOB_REMOVED
EVENT_JOB_MODIFIED
EVENT_JOB_EXECUTED
EVENT_JOB_ERROR
EVENT_JOB_MISSED
EVENT_JOB_SUBMITTED
EVENT_JOB_MAX_INSTANCES
Listener表示用户自定义监听的一些Event,比如当Job触发了EVENT_JOB_MISSED事件时可以根据需求做一些其他处理。
调度器
Scheduler是APScheduler的核心,所有相关组件通过其定义。scheduler启动之后,将开始按照配置的任务进行调度。除了依据所有定义Job的trigger生成的将要调度时间唤醒调度之外。当发生Job信息变更时也会触发调度。
APScheduler支持的调度器方式如下,比较常用的为BlockingScheduler和BackgroundScheduler
BlockingScheduler:适用于调度程序是进程中唯一运行的进程,调用start函数会阻塞当前线程,不能立即返回。
BackgroundScheduler:适用于调度程序在应用程序的后台运行,调用start后主线程不会阻塞。
AsyncIOScheduler:适用于使用了asyncio模块的应用程序。
GeventScheduler:适用于使用gevent模块的应用程序。
TwistedScheduler:适用于构建Twisted的应用程序。
QtScheduler:适用于构建Qt的应用程序。
- BlockingScheduler:适用于调度程序是进程中唯一运行的进程,调用 start函数会阻塞当前线程,不能立即返回。
- BackgroundScheduler:适用于调度程序在应用程序的后台运行,调用 start后主线程不会阻塞。
- AsyncIOScheduler:适用于使用了 asyncio模块的应用程序。
- GeventScheduler:适用于使用 gevent模块的应用程序。
- TwistedScheduler:适用于构建 Twisted的应用程序。
- QtScheduler:适用于构建 Qt的应用程序。
任务操作:
添加任务:
使用 scheduler.add_job(job_obj,args,id,trigger,**trigger_kwargs)。
删除任务:
使用 scheduler.remove_job(job_id,jobstore=None)。
暂停任务:
使用 scheduler.pause_job(job_id,jobstore=None)。
恢复任务:
使用 scheduler.resume_job(job_id,jobstore=None)。
修改某个任务属性信息:
使用 scheduler.modify_job(job_id,jobstore=None,**changes)。
修改单个作业的触发器并更新下次运行时间:
scheduler.reschedule_job(job_id,jobstore=None,trigger=None,**trigger_args)
输出作业信息:
使用 scheduler.print_jobs(jobstore=None,out=sys.stdout)
异常监听
当我们的任务抛出异常后,我们可以监听到,然后把错误信息进行记录。示例代码如下:
from apscheduler.schedulers.blocking import BlockingScheduler
from apscheduler.events import EVENT_JOB_EXECUTED, EVENT_JOB_ERROR
import datetime
import logging
配置日志显示
Scheduler的工作流程
Scheduler添加job流程:
Scheduler调度流程:
linux crontab实现
- python实现task
- shell脚本调用python
- 定时执行shell脚本
crontab命令:crontab