APScheduler 3.X版本 - 中文译本

背景:工作使用python + django + apscheduler + celery +MySQL + redis,某次想要使用异步的结果资料不全,所以想要搞一搞,问题是中文译本也不太好,为了提升熟练度,自己也来一套

时间:20240822

目的:后期会不定更新其内容,增加示例

原文网址:https://apscheduler.readthedocs.io/en/3.x/userguide.html

用户指南

安装 APScheduler

推荐的安装方法是使用pip

pip install apscheduler

如果你的pip尚未安装,你可以轻松安装它,通过下载并运行 get-pip.py 

如果因为某些原因,pip无法工作(未安装或安装后无法执行),你可以从pypi网站手动下载APScheduler模块,解压(提取)后安装它

python setup.py install

 代码示例

源代码中包含了示例文件夹,在这个文件夹中你可以找到很多以不同方式使用APscheduler的工作示例。这些例子你也可以在网上浏览,地址为:APscheduler 使用示例

基础概念

APscheduler有四种组件:

  • triggers                --        触发器

  • job stores            --        作业存储器

  • executors            --        执行器

  • schedulers          --        调度器

触发器包含调度逻辑,每一个任务(原文为job,此处译为任务)有它自己的触发器,触发器决定了任务下次运行的时间。除了初始配置,触发器是完全无状态的

作业存储器存放了需要调度的任务,默认的作业存储器仅是简单的保存在内存中,其他的作业存储器是各种各样的数据库。任务数据被持久化保存时,需要序列化,当加载回来时需要被反序列化。作业存储器(除默认的以外)不会将数据保存在内存中,而是作为中间人,在后端实现保存,更新,加载,搜索任务。作业存储器绝不能再多个调度器间共享。

执行器的作用是处理运行中的任务,他们通常通过将指定的可调用对象作为一个任务提交给线程池或进程池来完成这项操作。工作完成后,执行器通知调度器,然后调度器发出适当的事件信号。

调度器通常将其他部分捆绑在一起。在应用程序中,通常只有一个调度器。应用程序开发人员通常不直接处理任务存储、执行器或触发器。相反,调度器提供了适当的接口来处理所有这些。配置任务存储器和执行器是通过调度器完成的,添加、修改和删除任务也是如此。

选择正确的调度器

调度器的选择主要取决于编程环境和使用APScheduler的目的。以下是选择调度器的快速指南:

  • BlockingScheduler:在调度器是进程中唯一运行的内容时使用
  • BackgroundScheduler:不使用以下任何框架,并希望调度器在应用程序内部后台运行时使用
  • AsyncIOScheduler:如果应用程序使用asyncio模块,请使用
  • GeventScheduler:如果应用程序使用gevent,请使用
  • TornadoScheduler:如果正在构建Tornado应用程序,请使用
  • TwistedScheduler:如果正在构建Twisted应用程序,请使用
  • QtScheduler:如果正在构建Qt应用程序,请使用

足够简单吗?

选择正确的作业存储器

要选择合适的任务存储器,需要确定是否需要任务持久性。如果在应用程序开始时总是重新创建任务,那么可以使用默认的(MemoryJobStore);如果需要任务在调度器重启或应用程序崩溃中持续存在,那么选择通常归结于编程环境中使用的工具。当然,如果可以自由选择,推荐选择使用PostgreSQL后端的SQLAlchemyJobStore,因为它有强大的数据完整性保护。

选择正确的执行器

同样,如果使用上述框架之一,通常会自动选择执行器。否则,默认的ThreadPoolExecutor应该足够满足大多数需求。如果工作负载涉及CPU密集型操作,应该考虑使用ProcessPoolExecutor来利用多个CPU核心。甚至可以同时使用两者,将进程池执行器添加为辅助执行器。

选择正确的触发器

当计划一个任务时,需要为其选择一个触发器。触发器决定了任务运行日期/时间的逻辑。APScheduler自带三种内置触发器类型:

date:当您希望在某个特定时间点仅运行一次任务时使用。

interval:当您希望以固定的时间间隔运行任务时使用。

cron:当您希望在一天中的某个(些)特定时间定期运行任务时使用。

还可以将多个触发器组合成一个,这样当所有参与的触发器都同意的时间,或者任何一个触发器会触发时,它就会触发。有关组合触发器的更多信息,请参阅相关文档。也可以在各自的API文档页面上找到每个任务存储、执行器和触发器类型的插件名称。

配置调度器

APScheduler 提供了许多不同的方式来配置调度器。您可以使用配置字典,或者以关键字参数的形式传入选项。您还可以先实例化调度器,添加任务,然后再配置调度器。这样,您可以在任何环境中获得最大的灵活性。

完整的调度器级别配置选项列表可以在 BaseScheduler 类的 API 参考中找到。调度器子类也可能有额外的选项,这些选项在它们各自的 API 参考中有文档说明。各个任务存储和执行器的配置选项也可以在它们的 API 参考页面上找到。

假设您想在应用程序中使用默认的任务存储和默认的执行器运行 BackgroundScheduler:

from apscheduler.schedulers.background import BackgroundScheduler


scheduler = BackgroundScheduler()

# Initialize the rest of the application here, or before the scheduler initialization

这将配置一个带有名为“default”的MemoryJobStore和名为“default”的ThreadPoolExecutor(默认最大线程数为10)的BackgroundScheduler。

现在,假设想要更多。希望使用两个执行器拥有两个任务存储,并且还想调整新任务的默认值并设置不同的时区。以下三个示例完全等效,将提供:

一个名为“mongo”的MongoDBJobStore

一个名为“default”(使用SQLite)的SQLAlchemyJobStore

一个名为“default”的ThreadPoolExecutor,工作线程数为20

一个名为“processpool”的ProcessPoolExecutor,工作线程数为5

调度器的时区设置为UTC

默认情况下关闭新任务的合并

新任务的默认最大实例限制为3

方法1:

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)

方法2:

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',
})

方法3:

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()

# .. do something else here, maybe add jobs etc.

scheduler.configure(jobstores=jobstores, executors=executors, job_defaults=job_defaults, timezone=utc)

启动调度器

启动调度器只需在调度器上简单地调用start()方法。对于BlockingScheduler之外的其他调度器,此调用将立即返回,您可以继续应用程序的初始化过程,可能会向调度器添加任务。

对于BlockingScheduler,您只想在完成任何初始化步骤后才调用start()。

注意:调度器启动后,您将无法再更改其设置。

添加任务

将任务添加到调度器有两种方式:

1.通过调用add_job()方法

2.通过使用scheduled_job()装饰器修饰一个函数

第一种方式是最常见的做法。第二种方式主要用于声明在应用程序运行时不会更改的任务。add_job()方法返回一个apscheduler.job.Job实例,您可以稍后使用它来修改或删除任务。

您可以随时在调度器上安排任务。如果添加任务时调度器尚未运行,则该任务将被暂时安排,并且只有在调度器启动时才会计算其首次运行时间。

重要的是要注意,如果您使用序列化任务的执行器或任务存储,它会对您的任务增加一些要求:

1. 目标可调用对象必须是全局可访问的

2. 可调用对象的任何参数必须是可序列化的

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

重要:

如果在应用程序初始化期间在持久性任务存储中安排任务,您必须为任务定义一个明确的ID并使用replace_existing=True,否则每次应用程序重启时都会得到任务的一个新副本!

提示:

要立即运行作业,请在添加作业时省略参数。

移除任务

当从调度器中删除一个任务时,它会从其关联的任务存储中被移除,并且不会再被执行。有两种方法可以实现这一点:

1、通过使用任务的ID和任务存储别名调用remove_job()

2、通过对从add_job()获得的Job实例调用remove()

后一种方法可能更方便,但它要求您在添加任务时保存收到的Job实例。对于通过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')

暂停和恢复任务

可以通过Job实例或调度器本身容易的暂停和恢复任务。当一个任务被暂停时,其下一次运行时间会被清除,并且在任务恢复之前不会为其计算任何进一步的运行时间。要暂停一个任务,使用以下任一方法:

apscheduler.job.Job.pause()

apscheduler.schedulers.base.BaseScheduler.pause_job()

要恢复任务:

apscheduler.job.Job.resume()

apscheduler.schedulers.base.BaseScheduler.resume_job()

获取任务列表

要获取计划任务的机器可处理列表,可以使用get_jobs()方法。它将返回Job实例的列表。如果只对特定任务存储中的任务感兴趣,则可以在第二个参数中给出任务存储别名。

为了方便起见,您可以使用print_jobs()方法,它会打印出任务、触发器和下一次运行时间的格式化列表。

修改任务

您可以通过调用apscheduler.job.Job.modify()或modify_job()来修改任何任务属性。您可以修改除id之外的任何Job属性。

示例:

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

如果想重新安排任务——即更改其触发器,可以使用apscheduler.job.Job.reschedule()或reschedule_job()。这些方法为任务构造一个新的触发器,并根据新触发器重新计算其下一次运行时间。

示例:

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

关闭调度器

关闭调度器示例:

scheduler.shutdown()

默认情况下,调度器会关闭其作业存储器和执行器,并等待直到所有当前执行作业已完成。如果您不想等待,可以执行以下操作:

scheduler.shutdown(wait=False)

关闭作业存储和执行程序,但不会等待任何运行 要完成的任务

暂停和恢复调度器

暂停调度器是可以实现的

scheduler.pause()

这将导致调度器在进程恢复之前无法唤醒

scheduler.resume()

也可以在暂停状态下启动调度器,即不进行第一次唤醒调用:

scheduler.start(paused=True)

这在你需要在不希望的任务运行之前将其修剪掉时非常有用。

限制同时执行的任务实例数量

默认情况下,每个任务只允许同时运行一个实例。这意味着如果任务即将运行但前一次运行尚未完成,那么最近一次运行将被视为误触发。可以通过在添加任务时使用max_instances关键字参数来设置调度器允许特定任务同时运行的最大实例数。

错过作业执行和合并

有时,调度器可能无法在计划的时间执行预定的任务。最常见的情况是,当任务被安排在持久性任务存储中,并且在任务应该执行后调度器被关闭并重新启动时。当这种情况发生时,该任务被认为是“误触发”。然后,调度器将检查每个错过的执行时间与任务的misfire_grace_time选项(可以在每个任务的基础上设置或在调度器中全局设置)以查看是否仍应触发执行。这可能导致任务连续执行几次。

如果这种行为对你的特定用例不可取,可以使用合并将所有错过的执行合并为一次。换句话说,如果为任务启用了合并,并且调度器看到一个或多个排队的执行,它将只触发一次。对于“绕过”的运行,不会发送任何误触发事件。

注意:

如果由于线程池中没有可用的线程或进程而导致任务执行延迟,执行器可能会因为运行太晚(与最初指定的运行时间相比)而跳过它。如果您的应用程序中可能发生这种情况,您可能希望增加执行器中的线程/进程数量,或者将misfire_grace_time设置调整为更高的值。

执行器事件

可以为调度器附加事件监听器。调度器事件在某些情况下会被触发,并且可能会携带有关该特定事件的详细信息。通过将适当的掩码参数传递给add_listener()并使用OR将不同的常量组合在一起,可以仅侦听特定类型的事件。监听器可调用的参数是一个,即事件对象。

有关可用事件及其属性的详细信息,请参阅事件模块的文档。

示例

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)

故障排除

如果调度器没有按预期工作,将apscheduler日志记录器的日志级别提高到DEBUG级别会很有帮助。

如果您还没有启用日志记录,可以这样做:

import logging

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

这将提供大量有关调度器内部发生的情况的有用信息。

还要确保检查常见问题解答部分,以查看您的问题是否已有解决方案。

初版完成

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值