Python定时任务-APScheduler

目录

一,简介

1,安装APSchedule 3.6.0

2,APScheduler四个组件

二,选择调度器、作业存储、执行器和触发器

1,schedulers调度器选择

2,job stores作业存储选择

3,executors执行器选择

4,triggers触发器选择

三,调度器scheduler启动关闭和配置

1,启动调度器

2,关闭调度器

3,配置调度器

四,job操作

1,job添加

2,job移除

3,job暂停和恢复

4,job列表获取

5,job修改

五,简单示例运行

1,APScheduler 启用3步骤

2,blocking 类型调度器示例

3,background 类型调度器示例

4,触发器示例

六,其它

七,附录

1,整体结构

2,支持与以下5种框架集成


弹性伸缩需实现定时伸缩功能,对比了几个实现方案python-crontab、celery和APScheduler,最终选定APScheduler.

官方文档:https://apscheduler.readthedocs.io/en/v3.6.0/index.html

Github源码:https://github.com/agronholm/apscheduler

 

一,简介

Advanced Python Scheduler (APScheduler) 是一个Python库,可实现延迟调度要执行Python代码的功能,可以只执行一次,也可以定期执行。可以随时添加新任务或删除旧任务。如果将job任务存储在数据库中,这些任务还将在重新启动调度程序后保持它们的状态并继续运行。当重新启动调度程序时,它将运行离线时应该运行的所有job任务。

 

1,安装APSchedule 3.6.0

直接pip联网安装,或者下载离线包安装:https://pypi.org/project/APScheduler/3.6.0/ ,手动安装APScheduler,手动安装需要安装依赖包funcsigs、zlocal

$ pip install apscheduler

2,APScheduler四个组件

1,triggers触发器

包含调度逻辑,每一个job有它自己的触发器,用于决定job下一次运行时间。除了初始配置外,触发器完全是无状态的。

2,job stores作业存储

存储被调度的job,默认的job存储是简单地把job存储在内存中,其他的job存储是保存在数据库中。Job的数据在保存持久化存储时被序列化,并在加载时进行反序列化。job存储(默认存储除外)不将job数据保存在内存中,而是充当后台保存、加载、更新和搜索job的中间人。job存储永远不能在调度程序之间共享。

3,executors执行器

负责处理job的运行,通过将job指定的可调用对象 提交给一个线程或进程池来运行。当job完成时,执行器将会通知调度器,然后调度程序发出相应event

4,schedulers调度器

一个应用程序中通常只有一个调度器在运行,应用程序开发人员通常不会直接处理job存储、执行器和触发器,相反,调度器程序提供了处理这些事件的接口。

配置job存储和执行器都是在调度器中完成,例如添加、修改和移除job

 

二,选择调度作业存储、执行和触发器

 

1,schedulers调度器选择

对调度程序的选择主要取决于当前的编程环境,还有使用APScheduler的目的场景,以下7种调度器可选:

BlockingScheduler : 调度器在当前进程的主线程中运行,也就是会阻塞当前线程。

BackgroundScheduler : 调度器在后台线程中运行,不会阻塞当前线程。(在没有使用下面5个框架时使用

AsyncIOScheduler : 结合 asyncio 模块(一个异步框架)一起使用。

GeventScheduler : 程序中使用 gevent(高性能的Python并发框架)作为IO模型,和 GeventExecutor 配合使用。

TornadoScheduler : 程序中使用 Tornado(一个web框架)的IO模型。

TwistedScheduler : 配合 TwistedExecutor使用

QtScheduler : 配合 Qt 应用使用

 

2,job stores作业存储选择

要选择适当的作业存储,首先要确定是否需要作业数据持久化。如果总是在应用程序开始时重新创建作业,那么作业存储可以选择默认方式(MemoryJobStore)。否则,选择对应的持久化存储方式。jobstore提供对scheduler中job的增删改查接口,根据存储backend的不同,分以下几种

MemoryJobStore:没有序列化,jobs就存在内存里,增删改查也都是在内存中操作

SQLAlchemyJobStore:所有sqlalchemy支持的数据库都可以做为backend,增删改查操作转化为对应backend的sql语句

MongoDBJobStore:用mongodb作backend

RedisJobStore: 用redis作backend

RethinkDBJobStore: 用rethinkdb 作backend

ZooKeeperJobStore:用ZooKeeper做backend

 

3,executors执行器选择

如果使用上面的5个框架之一,通常选择对应框架的executor。否则,默认使用的ThreadPoolExecutor就可以满足大多数场景。如果工作涉及CPU密集型操作,则应该考虑使用ProcessPoolExecutor来充分利用多个CPU内核。可以同时使用这两种方法,将进程池执行器添加为辅助执行器。

最常用的两种executor :ProcessPoolExecutor 和 ThreadPoolExecutor,其它的还有AsyncIOExecutorDebugExecutor一种特殊的执行程序,直接执行可调用的目标,而不是将其延迟给线程或进程)、GeventExecutorTwistedExecutor

 

4,triggers触发器选择

调度一个job时,需要为它选择一个trigger触发器。触发器决定在运行job时计算日期/时间的逻辑。APScheduler有三种内置的触发器类型:

1)date: 指定某个确定的时间job仅执行一次
2)interval: 指定时间间隔(fixed intervals)周期性执行。
3)cron: 使用cron风格表达式周期性执行,用于(在指定时间内)定期运行job的场景

4)combining还可以将多个触发器组合成一个触发器,该触发器可以按所有参与触发器约定的时间触发,也可以在其中任何一个触发器将触发的时候触发。参考combiningAndTriggerOrTrigger实现。

 

三,调度器scheduler启动关闭和配置

1,启动调度器

启动调度器只需调用调度器上的start()。除了BlockingScheduler以外的调度程序,此调用将立即返回,可以继续应用程序的初始化过程,例如向调度程序添加作业。

对于BlockingScheduler,只需要在完成任何初始化步骤之后调用start()。

注意:启动调度程序后,不能再更改其设置。

2,关闭调度器

scheduler.shutdown()

默认情况下,调度程序关闭其作业存储和执行器,并等待所有当前执行的作业完成。如果你不想等,你可以执行:

scheduler.shutdown(wait=False)

这仍然会关闭作业存储和执行器,但不会等待任何正在运行的任务完成。

3,配置调度器

APScheduler提供了许多不同的方法来配置调度程序。可以使用配置字典,也可以将选项作为关键字参数传入。还可以先实例化调度器,然后添加作业并配置调度器。通过这种方式,可以为任何环境获得最大的灵活性。

1)默认配置:

假设在你的应用程序中运行BackgroundScheduler默认的job存储和默认的executors执行程序,如下:

from apscheduler.schedulers.background import BackgroundScheduler

scheduler = BackgroundScheduler()

此时将得到一个BackgroundScheduler实例,其中MemoryJobStore为“default”作业存储方法,ThreadPoolExecutor为“default”执行器,默认最大线程数为10。(参考apscheduler\executors\pool.py的ThreadPoolExecutor

2)高级配置:

希望使用两个executor拥有两个作业存储,还希望为新作业调整默认值并设置不同的时区。配置可实现如下:

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(),
    'default': SQLAlchemyJobStore(url='sqlite:///jobs.sqlite')
}
executors = {
    'default': ThreadPoolExecutor(20),
    'processpool': ProcessPoolExecutor(5)
}
job_defaults = {
    'coalesce': False,
    'max_instances': 3
}
scheduler = BackgroundScheduler(jobstores=jobstores, executors=executors, job_defaults=job_defaults, timezone=utc)

其中,url指定数据库的连接,运行后会直接在指定的数据库中添加apscheduler_jobs表,保存job相关信息。

 

四,job操作

Job是APScheduler的核心,其承接当前需要执行的工作和任务,可以在系统运行过程中动态地进行增加/修改/删除/查询等操作。

 

1,job添加

共有两种方式进行新增job的操作:

1)基于add_job来动态增加

代码示例:

sched.add_job(job_function, 'cron', day_of_week='mon-fri', hour=5, minute=30, end_date='2014-05-30')

2)基于修饰器scheduled_job来动态装饰job的实际函数

代码示例:

@sched.scheduled_job('cron', id='my_job_id', day='last sun')   

def some_decorated_task():

print("I am printed at 00:00:00 on the last Sunday of every month!")

内置的作业存储中,只有MemoryJobStore不会序列化作业。在内置的执行器中,只有ProcessPoolExecutor会序列化作业。

注意:如果在应用程序初始化期间在持久性作业存储中调度作业,则**必须**为作业定义显式ID,并使用'replace_existing=True'否则每次重新启动应用程序时,都会得到一份新的作业副本!

2job移除

可以通过job对象调用remove删除。

也可通过scheduler对象,指定job id调用remove_job删除;scheduler对象还可调用remove_all_jobs删除所有job。

示例1:

job = scheduler.add_job(myfunc, 'interval', minutes=2)

job.remove()

示例2:

scheduler.add_job(myfunc, 'interval', minutes=2, id='my_job_id')

scheduler.remove_job('my_job_id') 

3,job暂停和恢复

可以通过作业实例或调度程序本身对作业执行暂停和恢复操作。当作业暂停时,它的下一个运行时被清除,并且在作业恢复之前不会计算它的进一步运行时。

暂停作业:

apscheduler.job.Job.pause()

apscheduler.schedulers.base.BaseScheduler.pause_job()

恢复作业:

apscheduler.job.Job.resume()

apscheduler.schedulers.base.BaseScheduler.resume_job()

4,job列表获取

要获得调度作业处理列表,可以使用get_jobs()方法。它将返回所有job实例。如果只需要获取特定作业存储库中包含的作业,将作业存储别名作为第二个参数。

为了方便,可以使用print_jobs()方法,该方法将打印出格式化的作业列表、它们的触发器和下一次运行时。

apscheduler.get_jobs()

5,job修改

可以通过apscheduler.job.Job.modify() apscheduler.modify_job()修改除了id之外的job属性。例如:

job.modify(max_instances=6, name='Alternate name')

如果你想修改job的调度器,也就是说,改变它的触发器你可以使用apscheduler.job.Job.reschedule() reschedule_job()

scheduler.reschedule_job('my_job_id', trigger='cron', minute='*/5')

 

五,简单示例运行

 

1,APScheduler 3步骤

1)新建scheduler调度器(选择一种scheduler执行实例化操作)

2)向调度器添加一个job调度任务

3)运行job调度任务

 

2,blocking 类型调度器示例

演示使用blocking阻塞调度程序来调度每隔3秒执行一次的作业。

from datetime import datetime
import os
from apscheduler.schedulers.blocking import BlockingScheduler
def tick():
    print('Tick! The time is: %s' % datetime.now())

if __name__ == '__main__':
    scheduler = BlockingScheduler()
    scheduler.add_job(tick, 'interval', seconds=3)
    print('Press Ctrl+{0} to exit'.format('Break' if os.name == 'nt' else 'C'))
    try:
        scheduler.start()
    except (KeyboardInterrupt, SystemExit):
        pass

3,background 类型调度器示例

演示使用background后台调度程序来调度每隔3秒执行一次的作业。

from datetime import datetime
import time
import os
from apscheduler.schedulers.background import BackgroundScheduler
def tick():
    print('Tick! The time is: %s' % datetime.now())

if __name__ == '__main__':
    scheduler = BackgroundScheduler()
    scheduler.add_job(tick, 'interval', seconds=3)
    scheduler.start()
    print('Press Ctrl+{0} to exit'.format('Break' if os.name == 'nt' else 'C'))
    try:
        # This is here to simulate application activity (which keeps the main thread alive).
        while True:
            time.sleep(2)
    except (KeyboardInterrupt, SystemExit):
        # Not strictly necessary if daemonic mode is enabled but should be done if possible
        scheduler.shutdown()

4,触发器示例

1)date触发器,特定的时间点触发作业任务只会执行一次。

# 在 2019-03-29 14:00:00 时刻运行一次 job_func 方法
scheduler.add_job(job_func, 'date', run_date=datetime(2019, 3, 29, 14, 0, 0), args=['text'])

其中,run_date赋值类型可以为date/datetime对象,或符合ISO_8601时间格式的字符串。其它时间格式及场景格式如下:

sched.add_job(my_job, 'date', run_date=date(2019, 3, 29), args=['text'])
sched.add_job(my_job, 'date', run_date='2019-03-29 14:30:05', args=['text'])
sched.add_job(my_job, args=['text']) #立即运行

 2)interval 触发器固定时间间隔触发。

# 在 2019-03-29 14:00:01 ~ 2019-03-29 14:00:10 之间, 每隔两分钟执行一次job_func方法。
scheduler.add_job(job_func, 'interval', minutes=2, start_date='2019-03-29 14:00:01' , end_date='2019-03-29 14:00:10')

其中,start_dateend_date赋值类型可以为date/datetime对象,或符合ISO_8601时间格式的字符串。其它时间格式及场景格式如下:

sched.add_job(job_function, 'interval', hours=2) #持续定时触发
sched.add_job(job_function, 'interval', hours=1, jitter=120) #使用jitter参数,用于在执行时间中添加一个随机组件,用于多服务器场景中防止同时运行一个job的场景。此时将额外延迟[-120,+120]

3)cron 触发器在特定时间周期性地触发,和Linux crontab格式兼容

# 在2019-03-30 00:00:00之前,每周一到周五的5:30(am)触发
sched.add_job(job_function, 'cron', day_of_week='mon-fri', hour=5, minute=30, end_date='2019-03-30')

其中,start_dateend_date赋值类型可以为date/datetime对象,或符合ISO_8601时间格式的字符串。其它时间格式及场景格式如下:

sched.add_job(job_function, 'cron', month='6-8,11-12', day='3rd fri', hour='0-3')  #在六月七月八月十一月十二月的第三个周五的0点1点2点3点执行
sched.add_job(job_function, CronTrigger.from_crontab('0 0 1-15 may-aug *'))  #使用标准crontab表达式
sched.add_job(job_function, 'cron', hour='*', jitter=120)  #使用jitter参数,同上

 

六,其它

1,max_instances参数

限制一个作业并发执行实例的数量。(add_job方法)

默认情况下,每个作业只能同时运行一个实例。这意味着,如果有作业即将运行,但前一个运行尚未完成,则最新的运行将无效。通过在添加作业时使用max_instances关键字参数,可以为调度程序设置允许并发运行的特定作业的最大实例数。

2,misfire_grace_time参数

有时,调度程序可能无法在调度时执行计划的作业。最常见的情况是,在持久化作业存储中调度作业,并且在作业应该执行之后关闭并重新启动调度程序。当这种情况发生时,job被认为是“失败的”。调度程序将根据作业的misfire_grace_time选项(可以根据每个作业或调度程序中的全局设置该选项)检查每个错过的执行时间,以确定是否仍然应该触发执行。这可能导致作业连续执行几次。

如果你的特定用例不希望出现这种行为,那么可以使用coalescing 合并将所有这些未执行的操作合并到一起。换句话说,如果为作业启用了合并,并且调度程序看到作业的一个或多个队列执行,它将只触发一次。对于“bypassed”运行,不会发送无效事件。

如果一个作业的执行由于池中没有线程或进程可用而延迟,那么执行器可能会因为它运行得太晚而跳过它(与它最初指定的运行时相比)。如果在您的应用程序中可能发生这种情况,可以增加执行器中的线程/进程数量,或者将misfire_grace_time设置调整为更高的值。(add_job方法)

3,事件调度器

可以监听调度、任务执行情况相关的事件。

def my_listener(event):
    if event.exception:
        print('The job crashed :(')
    else:
        print('The job worked :)')
scheduler.add_listener(my_listener, EVENT_JOB_EXECUTED | EVENT_JOB_ERROR)

4,故障排查

如果调度器没有按照预期工作,可以将apscheduler日志记录器的日志级别提高到调试级别。

如果还没有启用日志功能,可以这样做:

import logging
logging.basicConfig()
logging.getLogger('apscheduler').setLevel(logging.DEBUG)

 

七,附录

 

1,整体结构

    apscheduler.events

    apscheduler.executors.asyncio

    apscheduler.executors.base

    apscheduler.executors.debug

    apscheduler.executors.gevent

    apscheduler.executors.pool

    apscheduler.executors.twisted

    apscheduler.job

    apscheduler.jobstores.base

    apscheduler.jobstores.memory

    apscheduler.jobstores.mongodb

    apscheduler.jobstores.redis

    apscheduler.jobstores.rethinkdb

    apscheduler.jobstores.sqlalchemy

    apscheduler.jobstores.zookeeper

    apscheduler.schedulers

    apscheduler.schedulers.asyncio

    apscheduler.schedulers.background

    apscheduler.schedulers.base

    apscheduler.schedulers.blocking

    apscheduler.schedulers.gevent

    apscheduler.schedulers.tornado

    apscheduler.schedulers.twisted

    apscheduler.triggers.base

    apscheduler.triggers.combining

    apscheduler.triggers.cron

    apscheduler.triggers.date

    apscheduler.triggers.interval

2,支持与以下5种框架集成

asyncio
gevent
Tornado
Twisted
Qt
 

参考:

https://apscheduler.readthedocs.io/en/latest/userguide.html

https://github.com/agronholm/apscheduler/tree/master/docs

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章