【Django杂记】定时任务框架APScheduler学习

1、APScheduler简介
  • 在平常的工作中几乎有一半的功能模块都需要定时任务来推动,例如项目中有一个定时统计程序,定时爬出网站的URL程序,定时检测钓鱼网站的程序等等,都涉及到了关于定时任务的问题,第一时间想到的是利用time模块的time.sleep()方法使程序休眠来达到定时任务的目的,虽然这样也可以,但是总觉得不是那么专业,所以就找到了Python的定时任务模块APScheduler。
  • APScheduler基于Quartz的一个Python定时任务框架,实现了Quartz的所有功能,使用起来十分方便。提供了基于日期、固定时间间隔以及crontab类型的任务,并且可以持久化任务。基于这些功能,我们可以很方便的实现一个Python定时任务系统。
  • APScheduler全称Advanced Python Scheduler;作用为在指定的时间规则执行指定的作业
    • 指定时间规则的方式可以是间隔多久执行,可以是指定日期时间的执行,也可以类似Linux系统中的Crontab中的方式执行任务
    • 指定的任务就是一个Python函数
2、安装
pip install apscheduler
3、APScheduler有四种组成部分:
  • 触发器(trigger):包含调度逻辑,每一个作业有它自己的触发器,用于决定接下来哪一个作业会运行。除了他们自己初始配置以外,触发器完全是无状态的
  • 作业存储(job store):作业存储器指定了作业被存放的位置,默认情况下作业保存在内存,也可将作业保存在各种数据库中,当作业被存放在数据库中时,它会被序列化,当被重新加载时会被反序列化。作业存储器充当保存、加载、更新和查找作业的中间商。在调度器之前不能共享作业存储。
  • 执行器(executors):执行器是将指定的作业(调用函数)提交到线程池或进程池中运行,当任务完成时,执行器通知调度器触发相应的事件。
  • 调度器(schedulers):任务调度器,属于控制角色,通过它配置作业存储器、执行器和触发器,添加、修改和删除任务。调度器协调触发器、作业存储器、执行器的运行,通常只有一个调度程序运行在应用程序中,开发人员通常不需要直接处理作业存储器、执行器或触发器,配置作业存储器和执行器是通过调度器来完成的。

4、APScheduler中几个重要的概念

4.1、Job作业
  • 作用:
    • Job作为APScheduler最小执行单位。
    • 创建Job时指定执行的函数,函数中所需参数,job执行时的一些设置信息
  • 构建说明:
    • id:指定作业的唯一ID
    • name:指定作业的名字
    • trigger:apscheduler定义的触发器,用于确定job的执行时间,根据设置的trigger规则,计算得到下次执行此job时间,满足时将会执行
    • executor:apscheduler定义的执行器,job创建时设置执行器的名字,根据字符串名字到scheduler获取到执行此job的执行器,执行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执行函数需要的关键字参数
  • 操作job操作
    • 添加job
      • 有两种添加方法,其中一种是add_job(),另外一种则是scheduled_job()修饰器来修饰函数
      • 这个两种办法的区别是:第一种方法返回一个apscheduler.job.Job的实例,可以用来改变或移除job。第二种方法只适用于应用运行期间不会改变的job
import datetime
from apscheduler.schedulers.background import BackgroundScheduler

def job_func():
    print("当前时间:", datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3])

# 创建调度器,通过它配置作业存储器、执行器和触发器,添加、修改和删除任务
scheduler = BackgroundScheduler(timezone='Asia/Shanghai')
scheduler.add_job(
    func=job_func, # 执行的函数
    trigger="interval", # 触发器
    seconds=2  # 2s执行一次
)

scheduler.start()
# 创建调度器,通过它配置作业存储器、执行器和触发器,添加、修改和删除任务
scheduler = BlockingScheduler(timezone='Asia/Shanghai')

# 定义执行的函数
@scheduler.scheduled_job(
    trigger='interval',  # 选择触发器
    seconds=2
)
def job_func():
    print("当前时间:", datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3])

scheduler.start()
    • 移除job
      • 移除job也有两种方法:remove_job()和job.remove()
      • remove_job()是根据job的id来移除,所系要在job创建的时候指定一个id
      • job.remove()则是对job执行remove方法即可
# 创建调度器,通过它配置作业存储器、触发器、执行器,添加、修改或删除任务
scheduler = BlockingScheduler(timezone='Asia/Shanghai')

# 定义执行的函数
def job_func():
    print("当前时间:", datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3])


# 添加job
scheduler.add_job(
    func=job_func,  # 执行函数
    trigger='interval',  # 触发器
    seconds=2,
    id='job_one',  # 指定作业的id
    name='test_job_one'  # 指定作业的名字
)

# 删除job, 需要传入创建job的id
scheduler.remove_job(job_id='job_one')
scheduler.start()
# 创建调度器,通过它配置执行器、触发器和作业存储器,添加、修改或删除作业
scheduler = BlockingScheduler(timezone='Asia/Shanghai')

# 定义执行的函数
def job_func():
    print("当前时间:", datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3])

job = scheduler.add_job(
    func=job_func,  # 执行函数
    trigger='interval',  # 触发器
    seconds=2,
    id='job_two',  # 指定作业的id
    name='test_job_two'   # 指定作业的名称
)
# 移除job
job.remove()

scheduler.start()
    • 获得任务列表
      • 可以通过get_jobs方法来获取当前的任务列表,也可以通过get_job()来根据job_id获得某个任务的信息。
      • 并且aspcheduler还提供了一个print_jobs()方法来打印格式化的任务列表
# 创建调度器,通过它来配置作业的存储器、执行器和触发器,添加、修改和删除作业
scheduler = BlockingScheduler(timezone='Asia/Shanghai')

# 定义执行的函数
def job_func1(name):
    print("我是:", name, "当前时间:", datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3])

# 添加任务
scheduler.add_job(
    func=job_func1,  # 执行的函数
    trigger='interval',  # 触发器
    seconds=2,
    id='job3',  # 指定作业的id
    name='test_job3',  # 指定作业的名称
    args=["小名"]  # 执行函数的参数
)

scheduler.add_job(
    func=job_func1,  # 执行的函数
    trigger='interval',  # 触发器
    seconds=2,
    id='job4',  # 指定作业的id
    name='test_job4',  # 指定作业的名称
    args=["小红"]  # 执行函数的参数
)

# 获取任务列表
jobs = scheduler.get_jobs()
print('jobs:', jobs)

# 根据job_id获取某个任务的详情
scheduler.get_job(job_id='job4')
# 打印格式化的任务列表
scheduler.print_jobs()

scheduler.start()
    • 修改job
      • 如果你因计划改变要对job进行修改,可以使用job.modify()或者modify_job()方法来修改除id以外的任何作业属性
job.modify(max_instances=6, name="Alternate name")
# 创建调度器,用来配置作业的存储器、触发器和执行器,可修改、删除、添加作业
scheduler = BlockingScheduler(timezone='Asia/Shanghai')

# 定义执行的函数
def job_func1(name):
    print("我是:", name, "当前时间:", datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3])

job = scheduler.add_job(
    func=job_func1,  # 执行函数
    trigger='interval',  # 触发器
    seconds=2,
    args=['小兰'],
    id="one1",
    name="one11"
)

# 第一种方式:
# job.modify(name="one111", args=['小兰兰'])
# print(scheduler.get_jobs())

# 第二种方式:
temp_dict = {'seconds': 3}
temp_trigger = scheduler._create_trigger(trigger='interval', trigger_args=temp_dict)
scheduler.modify_job(job_id='one1', trigger=temp_trigger)
scheduler.start()
    • 暂停与恢复任务
      • 暂停与恢复任务可以直接操作任务实例或者调度器来实现。当任务暂停时,它的运行时间会被重置,暂停期间不会计算时间
      • 暂停一个job,使用以下方法:
apscheduler.job.Job.pause()
apscheduler.schedulers.base.BaseScheduler.pause_job()
      • 而恢复一个job,使用以下方法:
apscheduler.job.Job.resume()
apscheduler.schedulers.base.BaseScheduler.resume_job()
# 创建调度器,用来配置作业的存储器、触发器和执行器,创建、修改和删除作用
scheduler = BackgroundScheduler(timezone='Asia/Shanghai')  # 非阻塞,后台运行

# 定义执行的函数
def job_func1():
    print("当前时间:", datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3])

job = scheduler.add_job(
    func=job_func1,  # 执行函数
    trigger='interval',  # 触发器
    seconds=1,
    id="two",  # 指定job的id
    name="two2"  # 指定job的名称
)
scheduler.start()
while True:
    # 第一种方式:
    time.sleep(3)
    # 暂停
    job.pause()
    time.sleep(1)
    # 恢复
    job.resume()
    
    # 第二种方式:
    time.sleep(3)
    scheduler.pause_job(job_id='two')  # 暂停
    time.sleep(1)
    scheduler.resume_job(job_id='two')  # 恢复
    • 启动调度器
      • 可以使用start()方法启动调度器,BlockingScheduler需要在初始化之后才能执行start(),对于其他的Scheduler调用start()方法都会直接返回,然后可以继续执行后面的初始化操作
from apscheduler.schedulers.blocking import BlockingScheduler

def job_func():
    print("hello world")

scheduler = BlockingScheduler()
scheduler.add_job(job_func, 'interval', seconds=1)
scheduler.start()
    • 关闭调度器:
      • 使用下边方法关闭调度器
scheduler.shutdown()
      • 默认情况下调度器会关闭它的任务存储和执行器,并等待所有正在执行的任务完成,如果不想等待,可以进行如下操作:
schedulershutdown(wait=False)
  • Trigger触发器
    • Trigger绑定到job,在scheduler调度筛选job时,根据触发器的规则计算出job的触发时间,然后与当前时间比较确定此job是否被执行,总之就是根据trigger规则计算出下一个执行时间
    • Trigger有多种种类,指定之间的DateTrigger,指定间隔时间的IntervalTrigger,像Linux的crontab一样的CronTrigger
    • 目前APScheduler支持触发器:
      • DateTrigger:在某个日期时间只触发一次事件
      • IntervalTrigger:想要在固定的时间间隔触发事件。
        • weeks:周
        • days:一个月中的第几天
        • hours:小时
        • minutes:分钟
        • seconds:秒
        • start_date:间隔触发的起始时间
        • end_date:间隔触发的结束时间
        • jitter:触发的时间误差
      • CronTrigger:在某个确切的时间周期性的触发实践
  • Executor执行器
    • 作用:调度器的主循环其实就是反复检查是不是有到时需要执行的任务,分以下几步完成:
      • 询问自己的每一个作业存储器,有没有到期需要执行的任务,如果有,计算这些作业中的每个作业需要运行的时间点,如果时间点有多个,做coalesce[合并执行]检查
      • 提交给执行器按时间点运行
    • Executor在scheduler中初始化,另外也可通过scheduler的add_executor动态添加Executor。每个executor都会绑定一个alias,这个作为唯一标识绑定到job,在实际执行时会根据job绑定的executor找到实际的执行器对象,然后根据执行器对象执行job
    • Executor的种类会根据不同的调度来选择,如果选择AsynicIO作为调度的库,那么选择AsyncIOExecutor,如果选择tornado作为调度的库,选择TornadoExecutor,如果选择启动进程作为调度,可以选择ThreadPoolExecutor或者ProcessPoolExecutor都可以
    • 执行器的选择也屈居于应用场景。通常默认的ThreadPoolExecutor已经足够好了。如果作业负载涉及CPU密集型操作,那么应该考虑使用ProcessPoolExecutor,甚至可以同时使用这两种执行器,将ProcessPoolExecutor执行器添加为二级执行器
    • apscheduler提供了许多不同的方法来配置调度器可以使用字典,也可以使用关键字参数传递。首先实例化调度程序,添加作业,配置调度器,获得最大的灵活性
    • 目前APScheduler支持的executor:
      • ThreadPoolExecutor:线程池执行器
      • ProcessPoolExecutor:进程池执行器
      • GeventExecutor:Gevent程序执行器
      • TornadoExecutor:Tornado程序执行器
      • TwistedExecutor:Twisted程序执行器
      • AsyncIOExecutor:asyncio程序执行器

    • 举例:
      • 如果我们调度程序在应用程序的后台运行,选择BackgroundScheduler,并使用默认的jobstore和默认的executor,则配置如下:
from apscheduler.schedulers.background import BackgroundScheduler
scheduler = BackgroundScheduler()
      • 如果我们想配置更多的信息,如设置两个执行器、两个作业存储器、调整新作为的默认值,并设置不同的时区;下面的三个方法是完全等同的。
        • 配置需求:
          • 配置名为mongo的MongoDBJobStore作业存储器
          • 配置名为default的SQLAlchemyJobStore(使用SQLite)作业存储器
          • 配置名为default的ThreadPoolExecutor,最大线程数为20
          • 配置名为processpool的ProcessPoolExecutor,最大进程数为5
          • UTC作为调度器的时区
          • coalesce默认情况下关闭
          • 作业的默认最大运行实例限制为3
# 方法一
from pytz import utc

from apscheduler.schedulers.background import BackgroundScheduler, BlockingScheduler
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(max_workers=20),
    'processpool': ProcessPoolExecutor(max_workers=5)
}

job_defaults = {
    'coalesce': False,
    'max_instances': 3
}

# 创建调度器
scheduler = BackgroundScheduler(jobstores=jobstores, executors=executors, job_defaults=job_defaults, timezone=utc)
# 方法二
from apscheduler.schedulers.background import BackgroundScheduler

# 创建调度器
scheduler = BackgroundScheduler({
    'apscheduler.jobstores.mongo': {
        'type': 'mongodb'
    },
    'apscheduler.jobstores.sqlalchemy': {
        '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'
})
# 方法三
# -*- coding: utf-8 -*-
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()

scheduler.configure(jobstores=jobstores, executors=executors, job_defaults=job_defaults, timezone=utc)
      • 以上涵盖了大多数情况的调度器配置,在实际运行时可以试试不同的配置会有怎样不同的效果。
  • Jobstore作业存储
    • Jobstore在scheduler中初始化,另外也可通过scheduler的add_jobstrore动态添加jobstore每个jobstore都会绑定一个alias,scheduler在Add Job时,根据指定的jobstore在scheduler中找到相应的jobstore,并将job添加到jobstore中。
    • jobstore主要是通过pickle库的loads和dumps【实现核心是通过python的__getstate__和__setstate__实现】,每次变更时将job动态保存到存储中,使用时再动态的加载出来,作为存储的可以是redis,也可以是数据库[通过sqlarchemy这个库集成多种数据库],也可以是mongodb等
    • 目前APScheduler支持的jobstrore:
      • MemoryJobStore:没有序列化,任务存储在内存中,增删改查都是在内存中完成
      • MongoDBJobStore:使用MongoDB作为存储器
      • RedisJobStore:使用redis作为存储器
      • RethinkDBStore
      • SQLAlchemyJobStore:使用SQlAlchemy这个orm框架作为存储方式
      • ZookeeperJobStore
  • 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监听事件
    • 可以给调度器添加实践监听器,调度器事件只有在某些情况下会被触发,并且可以携带某些有用的信息。通过给add_listener()传递合适的mask参数,可以只监听几种特定的事件类型,具体类型可看源码中的event.exception或者event.code值来识别判断
    • 使用add_listener添加监听事件,其中callback为监听事件触发后的回调函数,mark为Event事件
    • 举例:
import time

from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.events import EVENT_JOB_EXECUTED, EVENT_JOB_ERROR, EVENT_JOB_ADDED


def task_listener(event):
    if event.code == EVENT_JOB_EXECUTED:
        print('任务执行成功')
    if event.code == EVENT_JOB_ERROR:
        print('任务执行错误')
    if event.code == EVENT_JOB_ADDED:
        print('任务添加成功')


def my_job():
    print('nihao')
    print(1 / 0)


scheduler = BackgroundScheduler(timezone='Asia/Shanghai')
scheduler.add_listener(
    callback=task_listener,
    mask=EVENT_JOB_ERROR | EVENT_JOB_ADDED | EVENT_JOB_EXECUTED
)
job = scheduler.add_job(
    func=my_job,
    trigger='interval',
    seconds=1
)

scheduler.start()

while True:
    time.sleep(1)
    scheduler.add_job(
        func=my_job,
        trigger='interval',
        seconds=1
    )
    print('a:', scheduler.get_jobs())
    • 在生产环境中,可以把出错信息换成发送一封邮件或者发送一个短信,这样定时任务出错就可以立马就知道。
  • Scheduler调度器
    • Scheduler是APScheduler的核心,所有相关组件通过其定义。scheduler启动之后,将开始按照配置的任务进行调度。除了依据所有定义Job的trigger生成的将要调度时间唤醒调度之外。当发生Job信息变更时也会触发调度。
    • scheduler可根据自身的需求选择不同的组件,如果是使用Asyncio则选择AsyncIOScheduler,使用tornado则选择TornadoScheduler
    • 目前APScheduler支持的Scheduler:
      • BlockingScheduler:调度器在当前进程的主线程中运行,也就是会阻塞当前线程。
      • BackgroundScheduler:调度器在后台线程中运行,不会阻塞当前线程。
      • AsyncIOScheduler:结合asyncio模块(一个异步框架)一起使用
      • GeventScheduler:程序中使用gevent(高性能的Python并发框架)作为IO模型和GeventExecutor配合使用
      • TornadoScheduler:程序中使用Tornado(一个web框架)的IO模型,用ioloop.add_timeout完成定时唤醒
      • TwistedScheduler:配合TwistedExecutor,用reactor.callLater完成定时唤醒
      • QtScheduler:你的应用是一个Qt应用,需使用QTimer完成定时唤醒
5、Scheduler工作流程图
  • Scheduler添加job流程
  • scheduler调度流程
APScheduler使用示例
  • AsyncIO调度示例
import asyncio
import datetime

from apscheduler.events import EVENT_JOB_EXECUTED
from apscheduler.executors.asyncio import AsyncIOExecutor
from apscheduler.jobstores.redis import RedisJobStore  # 需要安装redis
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from apscheduler.triggers.interval import IntervalTrigger
from apscheduler.triggers.cron import CronTrigger

# 定义jobstore  使用redis 存储job信息
default_redis_jobstore = RedisJobStore(
    db=2,
    jobs_key="apschedulers.default_jobs",
    run_times_key="apschedulers.default_run_times",
    host="127.0.0.1",
    port=6379,
    password="test"
)

# 定义executor 使用asyncio是的调度执行规则
first_executor = AsyncIOExecutor()

# 初始化scheduler时,可以直接指定jobstore和executor
init_scheduler_options = {
    "jobstores": {
        # first 为 jobstore的名字,在创建Job时直接直接此名字即可
        "default": default_redis_jobstore
    },
    "executors": {
        # first 为 executor 的名字,在创建Job时直接直接此名字,执行时则会使用此executor执行
        "first": first_executor
    },
    # 创建job时的默认参数
    "job_defaults": {
        'coalesce': False,  # 是否合并执行
        'max_instances': 1  # 最大实例数
    }
}
# 创建scheduler
scheduler = AsyncIOScheduler(**init_scheduler_options)

# 启动调度
scheduler.start()

second_redis_jobstore = RedisJobStore(
    db=2,
    jobs_key="apschedulers.second_jobs",
    run_times_key="apschedulers.second_run_times",
    host="127.0.0.1",
    port=6379,
    password="test"
)

scheduler.add_jobstore(second_redis_jobstore, 'second')
# 定义executor 使用asyncio是的调度执行规则
second_executor = AsyncIOExecutor()
scheduler.add_executor(second_executor, "second")


# ***********               关于 APScheduler中有关Event相关使用示例               *************
# 定义函数监听事件
def job_execute(event):
    """
    监听事件处理
    :param event:
    :return:
    """
    print(
        "job执行job:\ncode => {}\njob.id => {}\njobstore=>{}".format(
            event.code,
            event.job_id,
            event.jobstore
        ))


# 给EVENT_JOB_EXECUTED[执行完成job事件]添加回调,这里就是每次Job执行完成了我们就输出一些信息
scheduler.add_listener(job_execute, EVENT_JOB_EXECUTED)


# ***********               关于 APScheduler中有关Job使用示例               *************
# 使用的是asyncio,所以job执行的函数可以是一个协程,也可以是一个普通函数,AsyncIOExecutor会根据配置的函数来进行调度,
# 如果是协程则会直接丢入到loop中,如果是普通函数则会启用线程处理
# 我们定义两个函数来看看执行的结果

def interval_func(message):
    print("现在时间: {}".format(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")))
    print("我是普通函数")
    print(message)


async def async_func(message):
    print("现在时间: {}".format(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")))
    print("我是协程")
    print(message)


# 将上述的两个函数按照不同的方式创造触发器来执行
# ***********               关于 APScheduler中有关Trigger使用示例               *************
# 使用Trigger有两种方式,一种是用类创建使用,另一个是使用字符串的方式
# 使用字符串指定别名, scheduler初始化时已将其定义的trigger加载,所以指定字符串可以直接使用


if scheduler.get_job("interval_func_test", "default"):
    # 存在的话,先删除
    scheduler.remove_job("interval_func_test", "default")

# 立马开始 2分钟后结束, 每10s执行一次 存储到first jobstore  second执行
scheduler.add_job(interval_func, "interval",
                  args=["我是10s执行一次,存放在jobstore default, executor default"],
                  seconds=10,
                  id="interval_func_test",
                  jobstore="default",
                  executor="default",
                  start_date=datetime.datetime.now(),
                  end_date=datetime.datetime.now() + datetime.timedelta(seconds=240))

# 先创建tigger
trigger = IntervalTrigger(seconds=5)

if scheduler.get_job("interval_func_test_2", "second"):
    # 存在的话,先删除
    scheduler.remove_job("interval_func_test_2", "second")
# 每隔5s执行一次
scheduler.add_job(async_func, trigger, args=["我是每隔5s执行一次,存放在jobstore second, executor = second"],
                  id="interval_func_test_2",
                  jobstore="second",
                  executor="second")

# 使用协程的函数执行,且使用cron的方式配置触发器

if scheduler.get_job("cron_func_test", "default"):
    # 存在的话,先删除
    scheduler.remove_job("cron_func_test", "default")

# 立马开始 每10s执行一次
scheduler.add_job(async_func, "cron",
                  args=["我是 每分钟 30s  时执行一次,存放在jobstore default, executor default"],
                  second='30',
                  id="cron_func_test",
                  jobstore="default",
                  executor="default")

# 先创建tigger
trigger = CronTrigger(second='20,40')

if scheduler.get_job("cron_func_test_2", "second"):
    # 存在的话,先删除
    scheduler.remove_job("cron_func_test_2", "second")
# 每隔5s执行一次
scheduler.add_job(async_func, trigger, args=["我是每分钟 20s  40s时各执行一次,存放在jobstore second, executor = second"],
                  id="cron_func_test_2",
                  jobstore="second",
                  executor="second")

# 使用创建trigger对象直接创建
print("启动: {}".format(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")))
asyncio.get_event_loop().run_forever()

常见问题

为什么scheduler不执行我的job
  • 导致这种情况的原因很多,最常见的两种情况是:
    • scheduler在uWSGI的工作进程中运行,但是uWSGI并没有启动多线程
    • 运行了BackgroundScheduler,但是已经执行到了脚本的末尾
    • 针对后一种情况,类似于这样的脚本是没有办法正常运行的:
from apscheduler.scheduler.background import BackgroundScheduler

def myjob():
    print('hello')
scheduler = BackgroundScheduler()
scheduler.start()
scheduler.add_job(myjob, 'cron', hour=0)
    • 可见,以上脚本在运行完add_job()之后就直接退出了,因此scheduler根本没有机会去运行其调度好的job
该如何在uWSGI中使用APScheduler
  • uWSGI使用了一些技巧来禁用掉GIL锁,但多线程的使用对于APScheduler的操作来说至关重要。为了修改这个问题,你需要使用--enable-threads选项来重新启用GIL
如何在一个或多个工作进程中共享独立的job store
  • 答案就是不可以;在两个或更多的进程中共享一个持久化的job store会导致scheduler的行为不正常:如重复执行或作业丢失等等。这个是因为APScheduler目前没有任何进程间同步和信号量机制,因此当一个job被添加、修改或从scheduler中移除时scheduler无法得到通知
  • 变通方案:在专用的进程中来运行scheduler,然后通过一些远程访问的途径--如RPyC、gRPC或一个HTTP服务器--来将其连接起来。
如何在web应用中使用APScheduler
  • 如果你想在Django中运行,可以考虑django-apscheduler,不过要注意,这个是第三方库而APScheduler的开发者不能保证其质量
  • 如果你想在Flask中使用APScheduler,这里也有一个非官方的插件Flask-APScheduler
  • 对于Pyramid用户而言,pyramid_scheduler可能更有用
  • 对于其他情况,你最好还是按照常理厂牌,使用BackgroundScheduler。如果你在一个异步的web框架如aiohttp中运行,你可能想使用别的scheduler以便充分利用框架的异步功能
APScheduler有图形用户界面吗?
  • 简单来说,官方的没有,以下的第三方库有它们自己的实现:
    • django_apscheduler
    • apschedulerweb
    • Nextdoor scheduler
posted @ 2022-05-31 13:40  郭祺迦  阅读(2367)  评论(0编辑  收藏  举报