使用Cerely调度任务

原文链接

开始使用Celery调度任务

如果引入调度,周期性执行或者不阻塞请求线程,很多Django应用可以得到更好的使用。

在Django应用里,有很多方式可以用来调度任务,但是使用Celery有许多优势。它有很好的支持,伸缩性好,并且能和Django一起工作。它应用广泛,有很多资源可以帮助学习和使用它。一旦学会了,对其他项目也有好处。

Celery 3.0.x

本文档适用于Celery 3.0.x。之前或之后的版本可能有差别。

Celery简介

Celery被用来稍后执行某些代码,或用调度器调度这些代码

这有什么用呢?下面有一些例子。

第一个例子,假设用户发来一个页面请求,然后等待请求完成,浏览器加载新页面。对于他们的请求,你需要运行一些代码,而这些代码的运行时间可能比用户想要等待网页的时间要长,但是你并不真得需要在响应页面请求前执行那些代码。这时,你如果使用Celery稍后执行这些耗时的代码,就立即响应页面请求。

当你需要连接远程服务器来处理请求时会经常遇到这种情况。你的应用不能控制远程服务器响应的时间,甚至远程服务器可能是关闭的。

另一种常见情形是需要周期性执行某些代码。比如,你可能每小时查询最新的天气预报并且储存数据。你可以写个任务来执行这项工作,然后设置Celery每个小时执行一次。这个任务运行并把数据存入数据库,然后Web应用就可以获得最新的天气预报。

一个任务(task)只是一个Python函数。你可以把调度一个任务理解为延时调用哪个函数。比如,你可以需要 Celery 5分钟后使用参数(1, 3, 3)调用你的函数task1,或者你可以使函数batchjob每晚0时执行。

我们将会设置Celery来使你的任务在和其余应用代码尽可能相似的环境中执行,以使它们使用同样的数据库和Django设置。有一些差异需要记住,稍后将会讲到。

当一个任务准备执行时,Celery将它放到一个队列上,这个队列上有其它的将要执行的任务。你可以有很多队列,但是这里为了简单我们假设只有一个队列。

可以这样说,将一个任务添加到队列上仅仅只是把它加入到一个待办列表。为了执行任务,一些其它的程序--任务执行单元(worker)--将监视队列上是否有任务。当它发现队列上有任务时,它会取出并执行第一个任务,然后接着监视队列等待其它任务。你可以有很多任务执行单元,可能在许多不同的服务器上,但是我们今天假设只有一个任务执行单元。

稍后我们会讲更多关于队列,任务执行单元和其他尚未提及的重要的程序,上面这些现在已经够用了,让我们做一些工作。

本地安装Cerely

安装本地Django使用的Celery很简单--只需安装django-celery


为Celery配置Django

首先,我们配置Celery在runserver上使用。对于Celery中间件(broker,稍后介绍),将使用Django database broker implementation。现在,你只需知道Celery需要一个中间件,在开发过程中可以使用Django自带的(但是在生产环境中你必须使用一些更健壮的性能更好的)

在Django settings.py文件中:

1.添加这些代码:


前两行是必需的,第三行设置Celery使用Django中间件。

注意:绝对不要在生产环境中使用Django中间件。在本教程中我们使用它只是为了节省时间。在生产环境中,你可以使用RabbitMQ或者Redis。

2.在INSTALLED_APPS中添加djcelery和kombu.transport.django:


djcelery是必需的。kombu.transport.django是基于Django的中间件,主要用在开发环境中。

3.创建Celery数据库,如果使用South作模式迁移:


否则:


编写一个任务

如前所述,一个任务可以仅仅只是一个Python函数。但是,Celery必须了解它。当Celery和Django一起工作时,这很容易。只需要在你的应用中添加一个tasks.py文件,把你的任务放在那个文件中,并且装饰它们。这里有一个简单的tasks.py:


当djcelery.setup_loader()从你的配置文件启动的时候,Celery将会检查INSTALLED_APPS查找tasks.py模块,找到那些被标记为任务的函数,并将它们注册为任务。

将一个函数标记为任务并不影响它正常工作。你仍可以如此调用它:z = add(1, 2)并且它会和以前一样工作。将它标记为任务可以让你用其它方式调用。

调度任务

接着上面的简单例子。我们想立即运行任务,并且不想它阻塞当前线程。只需通过在任务的名字后面添加 .delay 即可实现:


Celery将会把任务添加到它的队列 (“worker, please call myapp.tasks.add(2, 2)) 然后理解返回。只要一个空闲的任务执行单元在队列的首部看到它,任务执行单元就会把它从队列上移除,然后执行它:


一个关于导入名称的警告

这很重要,你的任务总是导入并且引用相同的包名称。比如,依赖于你的Python路径如何设置,可能这样指向它myproject.myapp.tasks.add或者myapp.tasks.add。或者从myapp.views,你可能这样导入它 .tasks.add。但是Celery无法知道这些都是同一个任务。

djcelery.setup_loader()将会使用你的应用在INSTALLED_APPS里的包名,加上 .tasks.functionname。确保当你调度你的任务,你也使用同样的名字导入它,否则可能会出现bugs。

测试

启动一个任务执行单元

正如我们提过的,一个单独的程序,任务执行单元,用来执行你的Celery任务。下面是我们如何启动一个任务执行单元满足开发需要。

首先,打开一个新终端或者新窗口。在终端里,设置相同的Django开发环境--启动你的虚拟环境或者把它们添加到你的Python路径里,这两种方法都可以使你使用runserver运行你的项目。

现在,你可以在那个终端里这样启动一个任务执行单元:


任务执行单元将会在那个窗口里运行,并且在那里显示输出。

运行任务

回到你的第一个窗口,启动一个Django终端,运行你的任务:


如果你在任务执行单元的窗口里看到这些输出,那么任务执行单元已经执行完那个任务。


一个例子

前面我们提到过使用Celery来避免对一个页面请求的响应延迟。下面是一个使用这种技术的简单的Django事例。

问题处理

尝试使Celery任务工作可能会很困难,因为会使用很多部分,并且这些部分之间还会相互联系。许多常见的小技巧仍然起作用:

  •   先使最简单的配置能够工作

  •   使用Python调试器以及输出语句来观察发生了什么

  •   提升日志级别(比如,将任务执行单元设置为—loglevel debug)来获得更多的信息

也有一些Celery专用工具。

Eager scheduling

在你的Django设置里,可以添加:


然后,Celery就会忽略全部调度机制,立即调用你的代码。

这就是说,设置了CELERY_ALWAYS_EAGER = True后,这两个语句变得一样:


你可以使用这种方法使你的核心内容在引入Celery调度问题之前开始工作。

查看队列

只要你在开发时使用Django自带的中间件,你的队列就存储在Django数据库里。这样你就可以很容易的查看它。在你的应用里向admin.py添加几行:


现在,你可以在/admin/django/message文件夹下查看队列上是否有任务。每一个信息(message)就是一个来自Celery的请求--让任务执行单元执行任务。信息的内容很难理解,但是有时候仅仅只知道你的任务是否在队列里就很有用了。信息会留在数据库里,所以看到很多信息并不意味着你的任务只在被执行。

检查结果

任何时候调度一个任务,Celery都会返回一个AsyncResult对象。你可以保存那个任务,然后稍后使用它检查任务是否执行完成,是否成功,以及结果。


周期性调度

另一个常见例子是周期性调度任务。Celery使用另一个程序celerybeat来实现。Celerybeat一直运行,等一个调度任务到执行时间了,celerybeat就会把它加入队列去执行。

显而易见,只有一个celerybeat程序可以运行(不像任务执行单元,只要你需要,你就可以运行任意个)

启动celerybeat和启动一个任务执行单元相似。打开另一个窗口,设置Django环境,然后:


有几种方法告诉celery在调度上运行一个任务。我们将使用在Django数据库表里存储计划任务这种方法。这使你很容易地修改计划任务,即使Django和Celery正在运行。

添加这条配置信息:


现在,你可以打开Django admin文件夹,前往/admin/djcelery/periodictask/文件夹添加计划任务。下面是一些字段的说明:

  •   Name—用来标记预订的任务

  •   Task(registered)-只要你在添加任务后启动Django至少一次,这里应该是任何一个你定义过的任务。如果你没有看到你想要的任务,最好检查原因,修复它,然后再使用写一个字段。

  •   Task(custom)-这里你可以键入任务的全称(例如,myapp.tasks.add),但是最好使用上面registered tasks字段

  •   Enabled-由于某些原因,比如暂停它,你不想要任务真得运行,你可以不选择它

  •   Interval-如果你想要任务间隔一段时间重复运行。你可能需要使用绿色的“+”定义一个新的计划任务。这很简单,例如,每5分钟运行,5设置为“Every”,“Period”设置为minutes。

  •   Crontab-如果任务要在指定时间运行,使用crontab替代Interval。使用绿色的“+”,填写minute, hour, day of week, day of month, and day of year。你可以使用“*”在任何字段替代一个具体值,但是注意-如果你使用“*”在minute字段,你的任务将会在选定的那个小时内的每个分钟里都执行。例如:每天早上7:30执行,设置minute为“30”,hour为“7”,其余字段为“*”。

  •   Arguments-如果需要为任务传入参数,你可以展开这部分,设置*args和**kwargs字段。

  •  Execution Oprions-高级设置,这里我们不会讨论。

默认计划任务

如果你想你的一些任务默认计划任务,不依赖于某人在安装完你的应用后在数据库里配置它们,你可以使用Django fixtures把你的计划任务作为应用的初始数据。

  • 在你的数据库里配置你想要的计划任务

  • 用json格式保存计划任务


       

  • 在你的应用里创建一个fixtures文件夹

  • 如果你再也不想编辑这些计划任务,你可以把你的json文件复制到fixtures文件夹中命名为initial_data.json。每次syncdb运行时,Django都会加载它,如果你在数据库里编辑了计划任务,你就会得到错误,或者失去更改。(你仍然可以添加新的计划任务,你只是不能更改从fixtures文件夹初始数据里加载的那些)

  • 如果你想把这些作为初始调度,把你的文件命名为其他的,当配置一个使用你应用的网站时加载它就可以了:

提示和技巧

不要把model对象传递给任务

因为任务不会立即运行,当一个任务运行并使用传入的model对象时,数据库的对应纪录可能已经改变。如果任务此时对model对象做了一些修改并且保存起来,数据库里的这些改变就会被旧数据覆盖了。

更安全的做法是保存对象,传递纪录的键名,在任务里重新获得对象。


在任务里调度其他任务

执行一个任务的时候调度另一个完全可行。这可以用于保证第二个任务在第一个任务做完一些必要的工作之前不会运行。

不要在一个任务里等待另一个任务

如果一个任务等待另一个任务,它的任务执行单元将一直阻塞而不能做其他事情直到等待结束。这早晚造成死锁。

如果你的任务A里想要调度任务B,在任务B结束之后,做一些其他的工作,最好创建一个任务C做这些工作,在任务B结束时调度任务C。

下一步

一旦你理解了这些基础部分,最好阅读一下Celery用户手册中的部分内容。我建议从这些章节开始;其他的要不和Django没有关系,要不太过高级:

在生产环境里使用Celery

这里描述的Celery配置是为了开发方便,绝不能应用在生产环境。

为了在生产环境里使用,最重要的修改是停止使用kombu.transport.djanggo作为中间件,使用RabbitMQ或者其他健壮的可伸缩的等价物替换。

英文原文:https://www.caktusgroup.com/blog/2014/06/23/scheduling-tasks-celery/
译者:CupKnight

 

评论

还没有任何评论,你来说两句吧

发表评论

浙ICP备16008686 -
善始者实繁,克终者盖寡