Celery 是一个异步任务队列。你可以使用它在你的应用上下文之外执行任务。总的想法就是你的应用程序可能需要执行任何消耗资源的任务都可以交给任务队列,让你的应用程序自由和快速地响应客户端请求。使用 Celery 运行后台任务并不像在线程中这样做那么简单。但是好处多多,Celery 具有分布式架构,使你的应用易于扩展。一个 Celery 安装有三个核心组件:

Celery 客户端: 用于发布后台作业。当与 Flask 一起工作的时候,客户端与 Flask 应用一起运行。

Celery workers: 这些是运行后台作业的进程。Celery 支持本地和远程的 workers,因此你就可以在 Flask 服务器上启动一个单独的 worker,随后随着你的应用需求的增加而新增更多的 workers。

消息代理: 客户端通过消息队列和 workers 进行通信,Celery 支持多种方式来实现这些队列。最常用的代理就是 RabbitMQ 和 Redis。

二、简单应用

通过一个简单例子,了解Flask的定时任务,项目名称:celery_demo,目录结构如下:
|---app
| |---__init__.py
| |---celery_manage
| | | ---- __init__.py
| | | ---- views.py
|---config.py
|---celery_worker.py

2. 编辑配置文件,config.py
 

import os
from datetime import timedelta
 
basedir = os.path.abspath(os.path.dirname(__file__))
 
class Config:
    SECRET_KEY = os.environ.get('SECRET_KEY') or 'hard to guess string'
    # 定时任务配置
    CELERY_BROKER_URL = 'redis://localhost:6379',
    CELERY_RESULT_BACKEND = 'redis://localhost:6379'
    # CELERY_TIMEZONE = 'Asia/Shanghai'
    CELERYBEAT_SCHEDULE = {
        # 定义任务名称:import_data
        # 执行规则:每10秒运行一次
        'import_data': {
            'task': 'import_data',
            'schedule': timedelta(seconds=10)
        },
    }
 
    @staticmethod
    def init_app(app):
        pass
 
class DevelopmentConfig(Config):
    DEBUG = True
    SQLALCHEMY_DATABASE_URI = os.environ.get('TEST_DATABASE_URL') or \
                              'sqlite:///' + os.path.join(basedir, 'data-dev.sqlite')
 
class TestingConfig(Config):
    TESTING = True
    SQLALCHEMY_DATABASE_URI = os.environ.get('TEST_DATABASE_URL') or \
                              'sqlite:///' + os.path.join(basedir, 'data-test.sqlite')
 
class ProductionConfig(Config):
    SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
                              'sqlite:///' + os.path.join(basedir, 'data.sqlite')
 
config = {
    'development': DevelopmentConfig,
    'testing': TestingConfig,
    'production': ProductionConfig,
    'default': DevelopmentConfig
}
 
3. 创建celery管理的蓝本,app/celery_manage/__init__.py

from flask import Blueprint
celery_manage = Blueprint('celery_manage', __name__)
from . import views
4. 初始化celery并注册蓝本到工厂函数,app/__init__.py

#!/usr/bin/python
# -*- coding: utf-8 -*-
from flask import Flask
from config import config
from flask_celery import Celery
 
# 创建Celery实例
celery = Celery()
 
def create_app(config_name):
    app = Flask(__name__)
    app.config.from_object(config[config_name])
    config[config_name].init_app(app)
    celery.init_app(app)
 
    #注册celery管理蓝本
    from .celery_manage import celery_manage as celery_manage_blueprint
    app.register_blueprint(celery_manage_blueprint)
 
    return app
5. 添加定时任务,app/celery_manage/views.py

#!/usr/bin/python
# -*- coding: utf-8 -*-
from app import celery
from celery.utils.log import get_task_logger
 
logger = get_task_logger(__name__)
 
# 定时导入
@celery.task(name="import_data")
def import_data():
    print "定时任务:每10秒执行一次"
    # 记录日志
    logger.info(u"导入成功")
注:任务名称为配置文件config.py中定义的名称,即“import_data”

6. 编辑启动应用的接口文件,celery_worker.py

#!/usr/bin/python
# -*- coding: utf-8 -*-
from app import create_app, celery
import os
 
application = create_app(os.getenv('FLASK_CONFIG') or 'default')
application.app_context().push()
 
if __name__ == '__main__':
    application.run()
7. 运行celery,在终端输入:

celery worker -l INFO -c 100 -A celery_worker.celery --beat

说明:

参数-l INFO可以详细输出任务信息。

celery默认会开启4个线程来处理任务,参数-c可以开启更多任务线程。

参数--beat:执行定时任务时, Celery会通过celerybeat进程来完成。Celerybeat会保持运行, 一旦到了某一定时任务需要执行时, Celerybeat便将其加入到queue中,适用于周期性任务。

APScheduler 使用起来还算是比较简单。运行一个调度任务只需要以下三部曲。

新建一个 schedulers (调度器) 。
 添加一个调度任务(job stores)。
 运行调度任务2.基础组件
APScheduler 有四种组件,分别是:调度器(scheduler),作业存储(job store),触发器(trigger),执行器(executor)。
schedulers(调度器)
 它是任务调度器,属于控制器角色。它配置作业存储器和执行器可以在调度器中完成,例如添加、修改和移除作业。triggers(触发器)
 描述调度任务被触发的条件。不过触发器完全是无状态的。job stores(作业存储器)
 任务持久化仓库,默认保存任务在内存中,也可将任务保存都各种数据库中,任务中的数据序列化后保存到持久化数据库,从数据库加载后又反序列化。executors(执行器)
 负责处理作业的运行,它们通常通过在作业中提交指定的可调用对象到一个线程或者进城池来进行。当作业完成时,执行器将会通知调度器。schedulers(调度器)
它提供 7 种调度器,能够满足我们各种场景的需要。例如:后台执行某个操作,异步执行操作等。调度器分别是:
BlockingScheduler : 调度器在当前进程的主线程中运行,也就是会阻塞当前线程。
BackgroundScheduler : 调度器在后台线程中运行,不会阻塞当前线程。
AsyncIOScheduler : 结合 asyncio 模块(一个异步框架)一起使用。
GeventScheduler : 程序中使用 gevent(高性能的Python并发框架)作为IO模型,和 GeventExecutor 配合使用。
TornadoScheduler : 程序中使用 Tornado(一个web框架)的IO模型,用 ioloop.add_timeout 完成定时唤醒。
TwistedScheduler : 配合 TwistedExecutor,用 reactor.callLater 完成定时唤醒。
QtScheduler : 你的应用是一个 Qt 应用,需使用QTimer完成定时唤醒。
triggers(触发器)
APScheduler 有三种内建的 trigger:
 1)date 触发器
 date 是最基本的一种调度,作业任务只会执行一次。它表示特定的时间点触发。它的参数如下:参数    说明
 run_date (datetime 或 str)    作业的运行日期或时间
 timezone (datetime.tzinfo 或 str)    指定时区
 # 在 2017-12-13 14:00:01 时刻运行一次 job_func 方法
 scheduler .add_job(job_func, 'date', run_date='2017-12-13 14:00:01', args=['text'])
 2)interval 触发器
 固定时间间隔触发。interval 间隔调度,参数如下:参数    说明
 weeks (int)    间隔几周
 days (int)    间隔几天
 hours (int)    间隔几小时
 minutes (int)    间隔几分钟
 seconds (int)    间隔多少秒
 start_date (datetime 或 str)    开始日期
 end_date (datetime 或 str)    结束日期
 timezone (datetime.tzinfo 或str)    时区
 # 在 2017-12-13 14:00:01 ~ 2017-12-13 14:00:10 之间, 每隔两分钟执行一次 job_func 方法
 scheduler .add_job(job_func, 
 'interval', minutes=2, 
 start_date='2017-12-13 14:00:01' , 
 end_date='2017-12-13 14:00:10')
 3)cron 触发器
 在特定时间周期性地触发,和Linux crontab格式兼容。它是功能最强大的触发器。
 我们先了解 cron 参数:参数    说明
 year (int 或 str)    年,4位数字
 month (int 或 str)    月 (范围1-12)
 day (int 或 str)    日 (范围1-31
 week (int 或 str)    周 (范围1-53)
 day_of_week (int 或 str)    周内第几天或者星期几 (范围0-6 或者 mon,tue,wed,thu,fri,sat,sun)
 hour (int 或 str)    时 (范围0-23)
 minute (int 或 str)    分 (范围0-59)
 second (int 或 str)    秒 (范围0-59)
 start_date (datetime 或 str)    最早开始日期(包含)
 end_date (datetime 或 str)    最晚结束时间(包含)
 timezone (datetime.tzinfo 或str)    指定时区
 # 在每年 1-3、7-9 月份中的每个星期一、二中的 00:00, 01:00, 02:00 和 03:00 执行 job_func 任务
 scheduler .add_job(job_func,
  'cron', month='1-3,7-9',day='0, tue', hour='0-3')


3. 简单任务

# coding:utf-8
from apscheduler.schedulers.blocking import BlockingScheduler
import datetime
 
def aps_test():
    print datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), '你好'
 
scheduler = BlockingScheduler()
scheduler.add_job(func=aps_test, trigger='cron', second='*/5')
scheduler.start()