Django中使用django-celery完成异步任务(1)

许多Django应用需要执行异步任务, 以便不耽误http request的执行. 我们也可以选择许多方法来完成异步任务, 使用Celery是一个比较好的选择, 因为Celery有着大量的社区支持, 能够完美的扩展, 和Django结合的也很好. Celery不仅能在Django中使用, 还能在其他地方被大量的使用. 因此一旦学会使用Celery, 我们可以很方便的在其他项目中使用它.

Celery介绍

  • Celery的主要用处是执行异步任务, 可以选择延期或定时执行功能
  • 适用于以下的场景
  • 第一, 假设用户正发起一个request, 并等待request完成后返回. 在这一request后面的view功能中, 我们可能需要执行一段花费很长时间的程序任务, 这一时间可能远远大于用户能忍受的范围. 当这一任务并不需要立刻执行时, 我们便可以使用Celery在后台执行, 而不影响用户浏览网页. 当有任务需要访问远程服务器完成时, 我们往往都无法确定需要花费的时间.
  • 第二则是定期执行某些任务. 比如每小时需要检查一下天气预报, 然后将数据储存到数据库中. 我们可以编写这一任务, 然后让Celery每小时执行一次. 这样我们的web应用便能获取最新的天气预报信息.

我们这里所讲的任务task, 就是一个Python功能(function). 定期执行一个任务可以被认为是延时执行该功能. 我们可以使用Celery延迟5分钟调用function task1, 并传入参数(1, 2, 3). 或者我们也可以每天午夜运行该function.

我们偏向于将Celery放入项目中, 便于task访问统一数据库和Django设置.

当task准备运行时, Celery会将其放入列队queue中. queue中储存着可以运行的task的list. 我们可以使用多个queue,

将任务task放入queue就像加入todo list一样. 为了使task运行, 我们还需要在其他线程中运行的苦工worker. worker实时观察着代运行的task, 并逐一运行这些task. 你可以使用多个worker, 通常他们位于不同服务器上.

安装Celery

pip install django-celery

django 中的配置

暂时使用django runserver来启动celery. 而Celery代理人(broker), 我们使用Django database broker implementation. 现在我们只需要知道Celery需要broker, 使用django自身便可以充当broker. (但在部署时, 我们最好使用更稳定和高效的broker, 例如Redis.)

在settings中配置

import djcelery
    djcelery.setup_loader()
    BROKER_URL = 'django://'
    ...
    INSTALLED_APPS = (
       ...
       'djcelery',
       'kombu.transport.django',
       ...
    )

第一二项是必须的, 第三项则告诉Celery使用Django项目作为broker.

在INSTALLED_APPS中添加的djcelery是必须的. kombu.transport.django则是基于Django的broker

最后创建Celery所需的数据表, 使用makemigrations 和 migrate 命令创建表结构

创建一个task任务

正如前面所说的, 一个task就是一个Pyhton function. 但Celery需要知道这一function是task, 因此我们可以使用celery自带的装饰器decorator: @task. 在django app目录中创建taske.py:

 from celery import task

    @task()
    def add(x, y):
        return x + y

当settings.py中的djcelery.setup_loader()运行时, Celery便会查看所有INSTALLED_APPS中app目录中的tasks.py文件, 找到标记为task的function, 并将它们注册为celery task.

将function标注为task并不会妨碍他们的正常执行. 你还是可以像平时那样调用它: z = add(1, 2).

开始执行task

让我们以一个简单的例子作为开始. 例如我们希望在用户发出request后异步执行该task, 马上返回response, 从而不阻塞该request, 使用户有一个流畅的访问过程. 那么, 我们可以使用.delay, 例如在在views.py的一个view中:

  from myapp.tasks import add
    ...
        add.delay(2, 2)
    ...

Celery会将task加入到queue中, 并马上返回. 而在一旁待命的worker看到该task后, 便会按照设定执行它, 并将他从queue中移除. 而worker则会执行以下代码:

    import myapp.tasks.add

    myapp.tasks.add(2, 2)

关于 import

这里需要注意的是, 在impprt task时, 需要保持一致. 因为在执行djcelery.setup_loader()时, task是以INSTALLED_APPS中的app名, 加.tasks.function_name注册的, 如果我们由于python path不同而使用不同的引用方式时(例如在tasks.py中使用from myproject.myapp.tasks import add形式), Celery将无法得知这是同一task, 因此可能会引起奇怪的bug.

测试

  • 启动worker
    正如之前说到的, 我们需要worker来执行task. 以下是在开发环境中的如何启动worker:

首先启动terminal, 如同开发django项目一样, 激活virtualenv, 切换到django项目目录. 然后启动django自带web服务器: python manage.py runserver.

然后启动worker:

python manage.py celery worker --loglevel=info

此时, worker将会在该terminal中运行, 并显示输出结果.

  • 启动task
  • 打开新的terminal, 激活virtualenv, 并切换到django项目目录:
 $ python manage.py shell
    >>> from myapp.tasks import add
    >>> add.delay(2, 2)

此时, 你可以在worker窗口中看到worker执行该task:

下面我们来看一个例子, 在views.py和tasks.py中:

# views.py
    from myapp.tasks import do_something_with_form_data

    def view(request):
        form = SomeForm(request.POST)
        if form.is_valid():
            data = form.cleaned_data
            # Schedule a task to process the data later
            do_something_with_form_data.delay(data)
        return render_to_response(...)
# tasks.py

    @task
    def do_something_with_form_data(data):
        call_slow_web_service(data['user'], data['text'], ...)

调试

由于Celery的运行需要启动多个部件, 我们可能会漏掉一两个. 所以我们建议:

使用最简单的设置
使用python debug和logging功能显示当前的进程

Eager模式

如果在settings.py设置:

    CELERY_ALWAYS_EAGER = True
那么Celery便以eager模式运行, 则task便不需要加delay运行:

    # 若启用eager模式, 则以下两行代码相同
    add.delay(2, 2)
    add(2, 2)

查看queue

因为我们使用了django作为broker, queue储存在django的数据库中. 这就意味着我们可以通过django admin查看该queue:

# admin.py
    from django.contrib import admin
    from kombu.transport.django import models as kombu_models

    admin.site.register(kombu_models.Message)

检查结果

每次运行异步task后, Celery都会返回AsyncResult对象作为结果. 你可以将其保存, 然后在将来查看该task是否运行成功和返回结果:

# views.py

result = add.delay(2, 2)
...
if result.ready():
    print "Task has run"
    if result.successful():
        print "Result was: %s" % result.result
    else:
        if isinstance(result.result, Exception):
            print "Task failed due to raising an exception"
            raise result.result
        else:
            print "Task failed without raising exception"
 else:
     print "Task has not yet run"

定期任务

还有一种Celery的常用模式便是执行定期任务. 执行定期任务时, Celery会通过celerybeat进程来完成. Celerybeat会保持运行, 一旦到了某一定期任务需要执行时, Celerybeat便将其加入到queue中. 不像worker进程, Celerybeat只有需要一个即可.

启动Celerybeat:

python manage.py celery beat

使Celery运行定期任务的方式有很多种, 我们先看第一种, 将定期任务储存在django数据库中. 即使是在django和celery都运行的状态, 这一方式也可以让我们方便的修改定期任务. 我们只需要设置settings.py中的一项便能开启这一方式:

# settings.py
CELERYBEAT_SCHEDULER = 'djcelery.schedulers.DatabaseScheduler'

然后我们便可以通过django admin的/admin/djcelery/periodictask/添加定期任务了.

Name: 这一定期任务的注册名
Task (registered): 可以选择所有已经注册的task之一, 例如前面的add function
Task (custom): task的全名, 例如myapp.tasks.add, 但最好还是用以上项
Enabled: 是否开启这一定期任务
Interval: 定期任务的间隔时间, 例如每隔5分钟
Crontab: 如果希望task在某一特定时间运行, 则使用Unix中的Crontab代替interval
Arguments: 用于传参数到task中

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值