Flask_Apscheduler定时器入门

前言

本文介绍的内容均是以程序中只启动了一个调度器为前提下。多个调度器在某些配置项上要多个参数指定调度器。

一、Flask_Apscheduler与Apscheduler

Apscheduler: 全名:Advanced Python Scheduler,是Python的一个定时任务框架,能按指定规则时间执行任务(python的函数),并能持久化任务至数据库,实现对定时任务的动态增、删、改、查操作。具备了一个合格定时器该有的所有功能。

Flask_Apscheduler: 是Flask框架的一个扩展库,增加了Flask对apScheduler的支持,即基于Flask框架使用习惯,稍微调整了一个Apscheduler的用法(配置导入,定时器启动,上下文管理),基本原理的概念与Apscheduler一致。

总结: 想要了解定时器框架的原理和用法,百度直接搜索Apscheduler即可。Flask_Apscheduler只是用于flask框架项目中时,部分方法有稍微变动而已,影响不大。

二、Apscheduler介绍

  • 要使用Apscheduler,当然是要先安装这个模块了,执行 pip install apscheduler

    2.1 四个基础组件

    2.1.1 调度器(schedulers)

    配置任务存储器和执行器都可以在调度器中完成,并对任务进行增、删、改、查操作,调度器通常是只有一个。Apscheduler提供的调度器有7种,我只用过 BlockingScheduler BackgroundScheduler

    BlockingScheduler : 调度器在当前进程的主线程中运行,也就是会阻塞当前线程。
  • BackgroundScheduler : 调度器在后台线程中运行,不会阻塞当前线程。 AsyncIOScheduler : 结合 asyncio 模块(一个异步框架)一起使用。 GeventScheduler : 程序中使用 gevent(高性能的Python并发框架)作为IO模型,和 GeventExecutor 配合使用。 TornadoScheduler : 程序中使用 Tornado(一个web框架)的IO模型,用 ioloop.add_timeout 完成定时唤醒。 TwistedScheduler : 配合 TwistedExecutor,用 reactor.callLater 完成定时唤醒。 QtScheduler : 你的应用是一个 Qt 应用,需使用QTimer完成定时唤醒。
    主进程调度器
    from apscheduler.schedulers.blocking import BlockingScheduler
    def function():
        print("定时任务跑起来了")
    # 定义主进程调度器
    scheduler = BlockingScheduler()
    # 添加任务,间隔5s执行一次func()函数。  看不懂没关系后面会介绍
    scheduler.add_job(func=function, trigger="interval", seconds=5)
    # 启动定时器
    scheduler.start()
    
    后台运行调度器
    from apscheduler.schedulers.background import BackgroundScheduler
    import time
    def function():
        print("定时任务跑起来了")
    # 定义后台执行调度器
    scheduler = BackgroundScheduler()
    # 添加任务,间隔5s执行一次func()函数。  看不懂没关系后面会介绍
    scheduler.add_job(func=function, trigger="interval", seconds=5)
    # 启动定时器
    scheduler.start()
    # 使主程序不关闭
    while True:
        print(time.sleep(9999))
    
    2.1.2 任务存储器(job stores)
  • 任务存储器是可以存储任务的地方,默认情况下任务保存在内存,服务重启后任务会消失,也可将任务保存在各种数据库中(mysql,redis,mongodb)。任务存储进去后,会进行序列化,然后也可以反序列化提取出来,继续执行。
  • # 数据持久化至Mysql
    from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore
    # 存储到sql文件
    jobstores = {
        'default': SQLAlchemyJobStore(url="sqlite:///jobs.sqlite")
    # 存储到现有的数据库
    jobstores = {
        'default': SQLAlchemyJobStore(url="mysql://root:root123@127.0.0.1:3306/sql'")
    # 数据持久化至MongoDB
    from apscheduler.jobstores.mongodb import MongoDBJobStore
    jobstores = {
        'default': MongoDBJobStore(host="127.0.0.1",port=27017, database="apscheduler", collection="jobs")
    # 数据持久化至redis
    from apscheduler.jobstores.redis import RedisJobStore 
    jobstores = {
        'default': RedisJobStore(host="127.0.0.1",port=6379, db=0, password="123456")
    # 引入配置的方法,在创建类时加入配置项
    scheduler = BackgroundScheduler(jobstores=jobstores)
    
    2.1.3 触发器(executors)

    1.date触发器:是最基本的一种调度,任务只会在指定日期时间执行一次。
    参数说明:
    run_date:任务的运行日期或时间 (datetime 或 str)
    timezone:指定时区(datetime.tzinfo 或 str)

    # 在2020年5月22日执行一次
    scheduler.add_job(func=func, trigger="date", run_date=date(2020, 5, 22), timezone="Asia/Shanghai")
    # 在2020年8月13日 14:00:01执行一次
    scheduler.add_job(func=func, trigger="date", run_date='2020-08-13 14:00:01')
    

    2.interval触发器:根据设置的时间间隔,每间隔执行一次。间隔开始计算时间,为任务创建时间。
    参数说明:
    weeks:间隔几周。int
    days:间隔天数。int。
    hours:间隔小时数。int。
    minutes:间隔分钟数。int。
    seconds:间隔秒数。int。
    start_date:间隔触发的起始时间。(datetime 或 str)
    end_date:间隔触发的结束时间。(datetime 或 str)
    timezone:指定时区。(datetime 或 str)

    # 每5秒执行一次func()函数
    scheduler.add_job(func=func, trigger="interval", seconds=5)
    # 在8月13~20日之间,每天执行一次
    scheduler.add_job(func=func, trigger="interval", days=1,start_date='2020-08-13 14:00:01', end_date='2020-08-20 14:00:01')
    

    3. crontab触发器:在特定时间周期性地触发,如每天,周循环等场景,它是功能最强大的触发器。

    参数范围只适用于int类型,str类型有无限可能,自己踩坑去吧
    year: 年,4位数字。int 或 str
    month: 月 (范围1-12)。int 或 str
    day: 日 (范围1-31)。int 或 str
    week:周 (范围1-53)。int 或 str
    day_of_week: 周内第几天或者星期几 (范围0-6,0是周一,6是周天 或者 mon,tue,wed,thu,fri,sat,sun)。int 或 str
    hour: 时 (范围0-23)。(int 或 str)
    minute: 分 (范围0-59)。(int 或 str)
    second: 秒 (范围0-59)。(int 或 str)
    start_date: 最早开始日期(包含)。(datetime 或 str)
    end_date: 最晚结束时间(包含)。(datetime 或 str)
    timezone: 指定时区。(datetime 或 str)

    # 每天23点定时执行
    scheduler.add_job(func=func, trigger="cron", day_of_week="0-6", hour=23 )
    # 在每年 1、2、3、7、8、9 月份中的每月第4天和星期日中的 00:00, 01:00, 02:00 和 03:00 执行 func 任务
    scheduler.add_job(func=func,trigger="cron",month="1-3,7-9",day="4th sun", hour="0-3")
    # 每周一早晨9点30分执行func任务
    scheduler.add_job(func=func, trigger="cron", day_of_week=0,  hour=9, minute=30)
    # 间隔5分钟执行一次,与interval触发器使用功能相同
    scheduler.add_job(func=func, trigger="cron", minute="*/5" )
    

    crontab触发器所支持的表达式

    4.执行器(schedulers):
    通常是设置线程池,由多个线程来执行不同的任务,我们的任务涉及到一些 CPU密集计算的操作,那就根据业务情况自行选择。我只用过ThreadPoolExecutor。

    ThreadPoolExecutor: 线程池执行器。 ProcessPoolExecutor: 进程池执行器。 GeventExecutor: Gevent程序执行器。 TornadoExecutor: Tornado程序执行器。 TwistedExecutor: Twisted程序执行器。 AsyncIOExecutor: asyncio程序执行器。
    # 配置线程池,支持最多20个线程同时执行
    from apscheduler.executors.pool import ThreadPoolExecutor
    executors = {
        'default': ThreadPoolExecutor(20) 
    # 配置进程池,支持最多3个进程同时执行
    from apscheduler.executors.pool import ProcessPoolExecutor
    executors = {
        'default': ProcessPoolExecutor(3) 
    #创建类,导入配置
    scheduler = BackgroundScheduler(executors=executors)
    

    5.任务相关配置:
    coalesce:是否合并执行任务默认为True。(bool值:True / False )。由于某种原因(系统挂了),定时任务未执行,比如任务A,每秒执行一次,系统挂了5s,积累了5次任务未执行。当系统重启后。
    设置为True,则只执行任务A一次。
    设置为False,则会将积累的任务都执行一次(5次)。

    max_instances:同一个任务同一时间最多只能有几个实例运行,默认值为1(int)。比如任务A,每3秒执行一次,但是执行一次任务A需要10s。
    设置值为1时,在A任务开始执行到第3s时,任务A仍然处于执行中,便不会再次触发任务A执行。
    设置值为2时,在任务A执行第3s时,会再次触发任务A执行,此时就有2个任务A处于执行中。即2个同任务实例一起执行。

    misfire_grace_time:添加允许容错的时间,单位为秒,默认值为1(int)。由于当前调度任务较多,线程池都被占满,则未被执行的任务,不会被调度器进行排队。只有当线程池被释放,才会再去检测未被执行的任务触发时间。此时就会判断,任务触发时间 - 当前时间 = 差值,是否小于所配置值。小于则任务仍然会被执行,否则任务将被跳过不执行。

    # 任务相关配置
    job_defaults = {
        # 不合并执行
        'coalesce': False,
        # 同一时间同个任务最大执行次数为3
        'max_instances': 3,
        # 任务错过当前时间60s内,仍然可以触发任务
        'misfire_grace_time':60
    # 创建类,导入配置
    scheduler = BackgroundScheduler(job_defaults=job_defaults)
    

    2.2动态配置任务

    2.2.1 新增任务

    方法:add_job()
    主要参数:
    func:定时任务执行的函数名称,注意是名称,不带括号的。
    args:任务执行函数的位置参数。(元组类型)例如args=(3,)
    id:任务id,唯一标识,修改,删除均以任务id作为标识
    trigger:触发器类型,参数可选:date、interval、cron
    replace_existing:将任务持久化至数据库中时,此参数必须添加,值为True。并且id值必须有。不然当程序重新启动时,任务会被重复添加。

    其他参数根据trigger设置的类型不同,可以设置响应的时间参数,具体参考2.1.3章节,以及一些不常用的自行百度即可。

    # 添加任务,间隔5s执行一次sch_func(a,b)函数。 
    scheduler.add_job(func=sch_func, args=("1","2"),id="1234", trigger="interval", seconds=5, replace_existing=True)
    
    2.2.2 编辑任务

    方法:modify_job()
    id:任务id,唯一标识,为需要修改的任务id
    func:定时任务执行的函数名称,注意是名称,不带括号的。
    args:任务执行函数的位置参数。(元组类型)例如args=(3,)
    trigger:触发器类型,参数可选:date、interval、cron

    跟添加任务的参数差不多,只是id为已存在的任务的id,如果用add_job()也能修改任务,只要id传的和已有任务id一致

    # 修改任务id为1234的任务,执行时间变为每20s执行一次
    scheduler.modify_job(id="1234",func=sch_func, args=("1","2"),trigger="interval", seconds=20 )
    # 用add_job()方式修改任务,将任务执行时间变为30s
    scheduler.add_job(func=sch_func, args=("1","2"),id="1234", trigger="interval", seconds=30, replace_existing=True)
    
    2.2.3 删除任务
    删除单个任务,参数为id
    remove_job(id)
    删除所有任务,参数可不填写
    remove_all_jobs()

    2.2.4 暂停任务

    方法:pause_job()
    参数id:为需要暂停的任务id

    # 任务id为1234的,暂停任务不执行
    scheduler.pause_job(id="1234")
    
    2.2.4 恢复任务

    方法:resume_job()
    参数id:为需要恢复的任务id

    # 任务id为1234的,恢复任务执行
    scheduler.resume_job(id="1234")
    
    2.2.5 查询任务

    方法:get_job(id="1234")
    参数id:为需要查询的任务id
    任务存在,则返回:60763f523fa26ffa0498a1ca (trigger: date[2021-04-14 10:55:32 CST], next run at: 2021-04-14 10:55:32 CST)
    类型为: <class 'apscheduler.job.Job'>
    任务不存在,则返回:None

    三、Flask_Apscheduler简单项目构建

    3.1安装Flask_Apscheduler模块

    pip install flask_apscheduler

    3.2 flask目录结构

    my_flask
    ├── app.py
    ├── utils
          └──schedules.py

    3.3 简单启动flask和定时器(定时器任务要自己在写代码添加)

    utils/schedules.py

    from apscheduler.jobstores.mongodb import MongoDBJobStore
    from apscheduler.executors.pool import ThreadPoolExecutor
    from flask_apscheduler import APScheduler as _BaseAPScheduler
    # 重写APScheduler,实现上下文管理机制,小优化功能也可以不要。对于任务函数涉及数据库操作有用
    class APScheduler(_BaseAPScheduler):
        def run_job(self, id, jobstore=None):
            with self.app.app_context():
                super().run_job(id=id, jobstore=jobstore)
    # 定时器配置项
    class SchedulerConfig(object):
        # 持久化配置,数据持久化至MongoDB
        SCHEDULER_JOBSTORES = {'default': MongoDBJobStore(host="127.0.0.1", port=27017,database="apscheduler",collection="jobs")}
        # 线程池配置,最大20个线程
        SCHEDULER_EXECUTORS = {'default': ThreadPoolExecutor(20)}
        # 调度开关开启
        SCHEDULER_API_ENABLED = True
        # 设置容错时间为 1小时
        SCHEDULER_JOB_DEFAULTS = {'misfire_grace_time':3600}
        # 配置时区
        SCHEDULER_TIMEZONE = 'Asia/Shanghai'
    scheduler = APScheduler()
    

    app.py

    from flask import Flask
    from utils.schedules import SchedulerConfig, scheduler
    app = Flask(__name__)
    # 导入定时器配置
    app.config.from_object(SchedulerConfig())
    # 初始化定时器
    scheduler.init_app(app)
    # 启动定时器,默认后台启动了
    scheduler.start()
    # 启动flask
    app.run()
    
    3.4 小贴士
  • 1.如果不重写APScheduler类,则定时任务函数涉及上下文操作时,app必须与定时任务函数在一个模块内,并且写成如下格式:
  • app.py

    from flask import Flask
    app = Flask(__name__)
    # 定时任务函数
    def fun():
        with app.app_context():