Python定时任务-schedule vs. Celery vs. APScheduler

在Python开发过程中我们经常需要执行定时任务,而此类任务我们通常有如下选项:

  • 自己造轮子
  • 使用schedule库
  • 使用Celery定时任务
  • 使用APScheduler

自己造轮子实现,最大的优势就是灵活性,调试方便,对于某些特定系统也许也是一种选择,不过对于大多数应用来说,我们应当尽可能地使用开源的成熟的方案。下面对后三种方案分别讨论:

使用schedule库

schedule库是一个轻量级的定时任务方案,优势是使用简单,也不需要做什么配置;缺点是无法动态添加任务,也无法将任务持久化。

安装
pip install schedule
使用
import schedule
import time

def job():
    print("I'm working...")

schedule.every(10).minutes.do(job)
schedule.every().hour.do(job)
schedule.every().day.at("10:30").do(job)
schedule.every(5).to(10).minutes.do(job)
schedule.every().monday.do(job)
schedule.every().wednesday.at("13:15").do(job)

while True:
    schedule.run_pending()
    time.sleep(1)

使用Celery

Celery在Python领域可谓大名鼎鼎,我们通常将Celery作为一个任务队列来使用,不过Celery也同时提供了定时任务功能。通常,当我们的解决方案中已经在使用Celery的时候可以考虑同时使用其定时任务功能,但是Celery无法在Flask这样的系统中动态添加定时任务(在Django中有相应的插件可以实现动态添加任务),而且如果对于不使用Celery的项目,单独为定时任务搭建Celery显得过于重量级了。(搭建Celery比较麻烦,还需要配置诸如RabbitMQ之类消息分发程序)。

Celery安装在此不再赘述,大家可以参考官网的资料

使用

Celery虽然无法动态添加定时任务,但是可以在程序固定位置添加定时任务,如下:

from celery import Celery
from celery.schedules import crontab

app = Celery()

# 此处on_after_configure装饰符意味着当Celery app配置完成之后调用该hook函数
@app.on_after_configure.connect
def setup_periodic_tasks(sender, **kwargs):
    # Calls test('hello') every 10 seconds.
    sender.add_periodic_task(10.0, test.s('hello'), name='add every 10')

    # Calls test('world') every 30 seconds
    sender.add_periodic_task(30.0, test.s('world'), expires=10)

    # Executes every Monday morning at 7:30 a.m.
    sender.add_periodic_task(
        crontab(hour=7, minute=30, day_of_week=1),
        test.s('Happy Mondays!'),
    )

@app.task
def test(arg):
    print(arg)
  • 这里调用add_periodic_task用于添加一个定时任务,相当于在Celery config文件中的beat_schedule设置项中添加了一项,如下:
    app.conf.beat_schedule = {
        'add-every-30-seconds': {
            'task': 'tasks.add',
            'schedule': 30.0,
            'args': (16, 16)
        },
    }
    
  • add_periodic_task中指定job function时需要用.s()来调用

使用APScheduler

笔者认为APScheduler是在实际项目最好用的一个工具库。它不仅可以让我们在程序中动态添加和删除我们的定时任务,还支持持久化,且其持久化方案支持很多形式,包括(Memory, MongoDB, SQLAlchemy, Redis, RethinkDB, ZooKeeper), 也可以非常好与一些Python framework集成(包括asyncio, gevent, Tornado, Twisted, Qt). 笔者所在的项目使用的是Flask框架,也有相应的插件可以供我们直接使用。

但是笔者没有使用插件,而是直接将APScheduler集成于项目代码中。

初始化scheduler
# 可以在初始化Flask的时候调用,并将返回的scheduler赋给app
def init_scheduler():
    # 这里用于持久化的设置,代码中演示使用MongoDB
    # client用于设置你自己的MongoDB的handler, 即MongoClient对象
    jobstores = {
        'default': MongoDBJobStore(client=your_db_handler, collection="schedule_job")
    }
    executors = {
        'default': ThreadPoolExecutor(20)
    }
    job_defaults = {
        'coalesce': False,
        'max_instances': 5
    }
    # 这里使用BackgroundScheduler即可
    scheduler = BackgroundScheduler(jobstores=jobstores, executors=executors, job_defaults=job_defaults, timezone=utc)
    # 注意这里一定要调用start启动scheduler
    scheduler.start()
    return scheduler
添加定时任务

APScheduler将定时任务分为三种:

  • interval: 比如每隔5分钟执行一次任务
  • cron: 比如每天早上5点执行一次任务
  • date: 比如在2018年5月5日执行一次任务

我们以添加cron job为例:

def test_job(name):
    print "hello, %s" % name
    
def add_daily_job(name):
    exec_time = datetime.now() + timedelta(minutes=2)
    hour = exec_time.strftime("%H")
    minute = exec_time.strftime("%M")
    # 这里要选择'cron'
    # 另外,job_id可以根据你自己的情况设定,其会被用于remove_job
    current_app.scheduler.add_job(
        test_job, 'cron', hour=hour, minute=minute,
        args=[name], id=job_id)
删除定时任务

通过在add_job时使用的job_id可以删除对应的定时任务。实际上在我们添加任务的时候,APScheduler会把相应的任务信息存储于我们jobstore中设置的持久化存储方案,这里使用的是MongoDB,然后当删除的时候会将相应的任务从MongoDB中删除。

def remove_daily_job(job_id):
    current_app.scheduler.remove_job(job_id)

总结:

APScheduler在实际使用过程中拥有最大的灵活性,可以满足我们的大部分定时任务的相关需求;Celery比较重量级,通常如果项目中已有Celery在使用,而且不需要动态添加定时任务时可以考虑使用;schedule非常轻量级,使用简单,但是不支持任务的持久化,也无法动态添加删除任务,所以主要用于简单的小型应用。

References

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容