一篇文章搞懂python定时任务-APScheduler

基本概念

APScheduler有四种组件:

  • triggers (触发器)
  • job stores (作业储存器)
  • executors (执行器)
  • schedulers (调度器)

触发器包含调度逻辑。每个作业都有自己的触发器,该触发器确定下一步应在何时运行该作业。除了其初始配置之外,触发器完全是无状态的。

作业储存器存放调度的作业。默认的作业存储器只是将作业保存的内存中,而其他作业则将他们存储在各种数据库中。作业的数据在保存到持久性作业存储中时会序列化,在从作业中加载回去时会反序列化。作业存储器(默认存储器除外)不会将作业数据保留在内存中,而是充当在后台保存,加载,更新和搜索作业的中间人。调度器之间绝对不能共享作业存储器。

执行器负责处理运行的作业。他们通常通过将作业中的指定可调用对象提交给线程或进程池来执行此操作。作业完成后,执行器通知调度器,发出响应事件。

调度器将其余部分绑定到一起。通常,您的应用程序中仅运行一个调度器。程序开发人员通常不直接处理作业存储器,执行器或触发器。而是调度器提供适当的接口来处理所有这些接口。配置作业存储器和执行器是通过调度器完成的,添加,修改和删除作业也是如此。

选择正确的调度器,作业存储器,执行器和触发器

调度器的选择主要取决于您的编程环境以及您将使用APScheduler做什么。
以下是选择调度器的快速指南:

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

要选择合适的作业存储器,您需要确定是否需要作业持久化。如果你总是在程序开始时重新创建作业,则可以使用默认设置(MemoryJobStore)。但是,如果你需要作业在调度器重新启动或应用程序崩溃后继续存在,那么你的选择通常可以归结为编程环境中使用的工具。但是,如果你可以自由选择,则建议使用PostgreSQL后端上的SQLAlchemyJobStore,因为它具有强大的数据完整性保护功能。

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

安排作业时,需要为其选择一个触发器。触发器确定运行作业时通过其计算日期/时间的逻辑。
APScheduler有三种内置触发器类型:

  • data: 当你想在某个时间点仅运行一次作业时使用
  • interval: 当你要以固定的时间间隔运行作业时使用
  • cron: 当你要在一天的特定时间定期运行作业时使用

也可以将多个触发器组合为一个触发器,该触发器可以在所有参与触发器约定的时间触发,也可以在任何触发器触发时触发。可见触发器文档。

配置调度器

APScheduler提供了很多不同的方式来配置调度器。你可以使用配置字典,也可以将选项作为关键字参数传递。你可以先实例化调度器,然后添加作业后配置调度器。这样,你可以在任何环境下获得最大的灵活性。
假设要使用默认的作业存储器和执行器运行BackgroundScheduler:

from apscheduler.schedulers.background import BackgroundScheduler

scheduler = BackgroundScheduler()

代码实例化一个BackgroundScheduler,它的MemoryJobStore为“default”,ThreadPoolExecutor为“default”,默认最大线程数为10。

现在假设你想要更多,想使用两个执行器创建两个作业存储器,还希望调整新作业的默认值并设置不同的时区。可以尝试下面的例子:

Method1:

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)

Method2:

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

Method3:

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. 通过使用scheduler_job()装饰器

常用第一种方法,第二种方法主要便于声明在程序运行期间不会更改的作业。add_job()方法返回apscheduler.job.Job实例,你可以再以后使用该实例来修改或删除作业。
你可以随时在调度器上调度作业。如果当在添加作业时调度器尚未运行,则将暂停调度作业,并且仅在调度器启动时才计算其首次运行时间。
重要的是要注意,如果使用执行器或作业存储器序列化作业,将对作业添加一系列要求:

  1. 可调用的目标对象必须是可全局访问的
  2. 可调用对象的任何参数都必须可序列化

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

如果你在程序初始化期间在持久化作业存储器中调度作业,则必须为该作业定义一个显式ID,并使用replace_existing=True,否则,程序每次重启时,都将获得该作业的新副本。

Tip
如果要立即运行作业,请在添加作业时省略触发器参数。

删除作业

当你从调度器中删除作业时,该作业将从其关联的作业存储器中删除,并且将不再执行。
有两种方法做这个:

  1. 通过使用作业ID和作业存储别名调用remove_job()
  2. 通过add_job()获得的Job实例上调用remove()

后一种方法可能更方便,但是它要求你将添加作业时收到的Job实例存储在某处。对于通过scheduled_job()调度作业,第一种方法是唯一的方法。

如果作业调度结束,则会自动删除作业。

如:

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

使用明确的jobID的作业:

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

暂停与恢复作业

你可以通过Job实例或调度器本身轻松地暂停和恢复作业。暂停作业时,将清除其下一个运行事件,直到恢复该作业之前,将不再为其计算其他运行时间。暂停作业使用以下方法:

  • apscheduler.job.Job.resume()
  • apscheduler.schedulers.base.BaseScheduler.pause_job()

恢复使用的方法:

  • apscheduler.job.Job.resume()
  • apscheduler.schedulers.base.BaseScheduler.resume_job()

获取被调度作业列表

要获取已调度作业的计算机可处理列表,可以使用get_jobs()方法。将返回Job实例列表。如果你只对特定作业存储器中包含的作业感兴趣,请给作业存储器设置别名作为第二个参数。

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

修改作业

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

如:

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

如果你想重新调度这个作业,更改其触发器即可,使用apscheduler.job.Job.reschedule()或reschedule_job()。这些方法为作业构造一个新的触发器,并根据新的触发器重新计算其下一次运行时间。
如:

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

关闭调度器

关闭调度器使用:

scheduler.shutdown()

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

scheduler.shutdown(wait=False)

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

暂停/恢复作业处理

可以暂停调度作业的处理:

scheduler.pause()

这将导致调度器在恢复处理之前不会唤醒

scheduler.resume()

也有可能在暂停状态下启动调度器,即没有第一个唤醒调用:

scheduler.start(paused=True)

这对于需要修剪不想要的作业之前运行很有用。

限制作业的并发执行实例数

默认情况下,每个作业仅允许一个实例同时运行。这意味着,如果该作业将要运行,但是上一次运行尚未完成,则最后运行的作业要考虑停止。通过在添加作业时使用max_instances关键字参数,可以为调度器允许并发执行的作业设置最大实例数。

调度器事件

可以将事件监听器附加到调度器,调度器事件在某些情况下会触发,并且可能在其中携带有关该特定事件的详细信息。通过为add_listener()提供适当的mask参数,或将不同的常数放到一起,可以只侦听特定类型的事件。使用一个参数调用监听器,参数为事件对象。

如:

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)

这应该会提供有关调度器内部发生的事情的许多有用信息。

在这里插入图片描述
最后欢迎大家扫描关注公众号号交流。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值