python apscheduler的使用研究

Scrapydweb-APScheduler定时任务分析

  • 看看是怎么运作的

APScheduler简介

  • APscheduler全称Advanced Python Scheduler
  • APScheduler是一个Python定时任务框架,
  • 提供了基于日期、固定时间间隔以及crontab类型的任务
  • 并且可以持久化任务
  • 基于这些功能,我们可以很方便的实现一个python定时任务系统。

为什么要使用这个APScheduler

  • linux 自带的 crontab ,windows 自带的任务计划,都可以实现守时任务。
    没错,操作系统基本都会提供定时任务的实现,
  • 但是如果你想要更加精细化的控制,或者说任务程序需要跨平台运行,最好还是自己实现定时任务框架,
    Python 的 apscheduler 提供了非常丰富而且方便易用的定时任务接口。
  • apscheduler 可以当作一个跨平台的调度工具来使用,
    可以做为 linux 系统crontab 工具或 windows 计划任务程序的替换。
    注意,apscheduler 不是一个守护进程或服务,它自身不带有任何命令行工具。
    它主要是要在现有的应用程序中运行,
    也就是说,apscheduler 为我们提供了构建专用调度器或调度服务的基础模块。

为什么要自己读官方文档

网上一搜索,晕死,好多写apscheduler的都是超级老的版本,
而且博客之间相互乱抄,错误一大堆。
还是自己读官方文档,为大家理一遍吧。

安装

  • pip install apscheduler

APScheduler有四种组成部分

作业 job

  • job这是一个核心概念
  • Job是APScheduler最小执行单位。
  • job指定的任务就是一个Python函数。
  • 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规则计算出下一个执行时间。
  • 目前支持三种触发器
  • DateTrigger
  • IntervalTrigger
  • CronTrigger

执行器 executor

  • 执行器(executor)处理作业的运行,他们通常通过在作业中提交制定的可调用对象
    到一个线程或者进城池来进行。当作业完成时,执行器将会通知调度器。
  • Executor在scheduler中初始化,另外也可通过scheduler的add_executor动态添加Executor。
  • 每个executor都会绑定一个alias,这个作为唯一标识绑定到Job,在实际执行时会根据Job绑定的executor
    找到实际的执行器对象,然后根据执行器对象执行Job
  • Executor的种类会根据不同的调度来选择,
    如果选择AsyncIO作为调度的库,那么选择AsyncIOExecutor,
    如果选择tornado作为调度的库,选择TornadoExecutor,
    如果选择启动进程作为调度,选择ThreadPoolExecutor或者ProcessPoolExecutor都可以
    Executor的选择需要根据实际的scheduler来选择不同的执行器
  • 目前APScheduler支持的Executor:
    AsyncIOExecutor
    GeventExecutor
    ThreadPoolExecutor
    ProcessPoolExecutor
    TornadoExecutor
    TwistedExecutor

调度器 scheduler

  • 调度器(scheduler)是其他的组成部分。你通常在应用只有一个调度器,
    应用的开发者通常不会直接处理作业存储、调度器和触发器,相反,
    调度器提供了处理这些的合适的接口。配置作业存储和执行器可以在调度器中完成,
    例如添加、修改和移除作业。 
    Scheduler 调度器
    Scheduler是APScheduler的核心,所有相关组件通过其定义。scheduler启动之后,将开始按照配置的任务进行调度。
    除了依据所有定义Job的trigger生成的将要调度时间唤醒调度之外。当发生Job信息变更时也会触发调度。

在配置调度器前,我们首先要选取适合我们应用环境场景的调度器,存储器和执行器。下面是各调度器的适用场景:
scheduler可根据自身的需求选择不同的组件。
目前APScheduler支持的Scheduler:
BlockingScheduler:适用于调度程序是进程中唯一运行的进程,调用start函数会阻塞当前线程,不能立即返回。
BackgroundScheduler:适用于调度程序在应用程序的后台运行,调用start后主线程不会阻塞。
AsyncIOScheduler:适用于使用了asyncio模块的应用程序。
GeventScheduler:适用于使用gevent模块的应用程序。
TwistedScheduler:适用于构建Twisted的应用程序。
QtScheduler:适用于构建Qt的应用程序。
ornadoScheduler:使用tornado则选择TornadoScheduler。
上述调度器可以满足我们绝大多数的应用环境,本文以两种调度器为例说明如何进行调度器配置。

其他

Jobstore 作业存储

作业存储器的选择有两种:
一是内存,也是默认的配置;
二是数据库。具体选哪一种看我们的应用程序在崩溃时是否重启整个应用程序,
如果重启整个应用程序,那么作业会被重新添加到调度器中,此时简单的选取内存作为作业存储器即简单又高效。
但是,当调度器重启或应用程序崩溃时您需要您的作业从中断时恢复正常运行,
那么通常我们选择将作业存储在数据库中,使用哪种数据库通常取决于为在您的编程环境中使用了什么数据库。
我们可以自由选择,PostgreSQL 是推荐的选择,因为它具有强大的数据完整性保护。

Jobstore在scheduler中初始化,另外也可通过scheduler的add_jobstore动态添加Jobstore。每个jobstore
都会绑定一个alias,scheduler在Add Job时,根据指定的jobstore在scheduler中找到相应的jobstore,并
将job添加到jobstore中。

Jobstore主要是通过pickle库的loads和dumps【实现核心是通过python的__getstate__和__setstate__重写
实现】,每次变更时将Job动态保存到存储中,使用时再动态的加载出来,作为存储的可以是redis,也可以
是数据库【通过sqlarchemy这个库集成多种数据库】,也可以是mongodb等

目前APScheduler支持的Jobstore:
MemoryJobStore
MongoDBJobStore
RedisJobStore
RethinkDBJobStore
SQLAlchemyJobStore
ZooKeeperJobStore

  • 作业存储(job store)存储被调度的作业,默认的作业存储是简单地把作业保存在内存中,
    其他的作业存储是将作业保存在数据库中。一个作业的数据讲在保存在持久化作业存储时被序列化,
    并在加载时被反序列化。调度器不能分享同一个作业存储。

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 监听事件

Listener表示用户自定义监听的一些Event,当Job触发了EVENT_JOB_MISSED事件时
可以根据需求做一些其他处理。

简单应用

import time
from apscheduler.schedulers.blocking import BlockingScheduler
 
def my_job():
    print time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time()))
 
sched = BlockingScheduler()
sched.add_job(my_job, 'interval', seconds=5)
sched.start()

上面的例子表示每隔5s执行一次my_job函数,输出当前时间信息

操作作业

1. 添加作业

  • 上面是通过add_job()来添加作业,另外还有一种方式是通过scheduled_job()修饰器来修饰函数
import time
from apscheduler.schedulers.blocking import BlockingScheduler
 
sched = BlockingScheduler()
 
@sched.scheduled_job('interval', seconds=5)
def my_job():
    print time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time()))
 
sched.start()

2. 移除作业

job = scheduler.add_job(myfunc, 'interval', minutes=2)
job.remove()
#如果有多个任务序列的话可以给每个任务设置ID号,可以根据ID号选择清除对象,且remove放到start前才有效
sched.add_job(myfunc, 'interval', minutes=2, id='my_job_id')
sched.remove_job('my_job_id')

3. 暂停和恢复作业

# 暂停作业:
apsched.job.Job.pause()
apsched.schedulers.base.BaseScheduler.pause_job()

# 恢复作业:
apsched.job.Job.resume()
apsched.schedulers.base.BaseScheduler.resume_job()

4. 获得job列表

  • 获得调度作业的列表,可以使用get_jobs()来完成,它会返回所有的job实例。
  • 或者使用print_jobs()来输出所有格式化的作业列表。
  • 也可以利用get_job(任务ID)获取指定任务的作业列表
job = sched.add_job(my_job, 'interval', seconds=2 ,id='123')
print sched.get_job(job_id='123')
print sched.get_jobs()

5. 关闭调度器

  • 默认情况下调度器会等待所有正在运行的作业完成后,关闭所有的调度器和作业存储。
  • 如果你不想等待,可以将wait选项设置为False。
sched.shutdown()
sched.shutdown(wait=False)

作业运行的控制(trigger)

(1). 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')

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

例子:
# 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'])

日志

好了,scheduler的基本应用,我想大家已经会了,但这仅仅只是开始。
如果代码有意外咋办?会阻断整个任务吗?如果我要计算密集型的任务咋办?
下面有个代码,我们看看会发生什么情况。

意外

任何代码都可能发生意外,关键是,发生意外了,如何第一时间知道,这才是公司最关心的,
apscheduler已经为我们想到了这些。
是不是很直观,在生产环境中,你可以把出错信息换成发送一封邮件或者发送一个短信,
这样定时任务出错就可以立马就知道了。
好了,今天就讲到这,以后我们有机会再来拓展这个apscheduler,这个非常强大而且直观的后台任务库。

posted @ 2021-10-28 00:13  技术改变命运Andy  阅读(383)  评论(0编辑  收藏  举报