jango中如何使用django-celery完成異步任務 (1)

本篇博文主要介紹在開發環境中的celery使用,請勿用於部署服務器.

許多Django應用需要執行異步任務, 以便不耽誤http request的執行. 我們也可以選擇許多方法來完成異步任務, 使用Celery是一個比較好的選擇, 因爲Celery有着大量的社區支持, 能夠完美的擴展, 和Django結合的也很好. Celery不僅能在Django中使用, 還能在其他地方被大量的使用. 因此一旦學會使用Celery, 我們可以很方便的在其他項目中使用它.

1. Celery版本

本篇博文主要針對Celery 3.0.x. 早期版本的Celery可能有細微的差別.

2. 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, 通常他們位於不同服務器上. 同樣爲了簡單起見, 我們這只是用一個worker.

我們稍後會討論queue, worker和另外一個十分重要的進程, 接下來我們來動動手:

3. 安裝Celery

我們可以使用pip在vietualenv中安裝:

    pip install django-celery

4. Django設置

我們暫時使用django runserver來啓動celery. 而Celery代理人(broker), 我們使用Django database broker implementation. 現在我們只需要知道Celery需要broker, 使用django自身便可以充當broker. (但在部署時, 我們最好使用更穩定和高效的broker, 例如Redis.)

在settings.py中:

    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所需的數據表, 如果使用South作爲數據遷移工具, 則運行:

    python manage.py migrate

否則運行: (Django 1.6或Django 1.7都可以)

    python manage.py syncdb

5. 創建一個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).

6. 執行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)

7. 關於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.

8. 測試

a. 啓動worker

正如之前說到的, 我們需要worker來執行task. 以下是在開發環境中的如何啓動worker:

首先啓動terminal, 如同開發django項目一樣, 激活virtualenv, 切換到django項目目錄. 然後啓動django自帶web服務器: python manage.py runserver.

然後啓動worker:

    python manage.py celery worker --loglevel=info

此時, worker將會在該terminal中運行, 並顯示輸出結果.

b. 啓動task

打開新的terminal, 激活virtualenv, 並切換到django項目目錄:

    $ python manage.py shell
    >>> from myapp.tasks import add
    >>> add.delay(2, 2)

此時, 你可以在worker窗口中看到worker執行該task:

    [2014-10-07 08:47:08,076: INFO/MainProcess] Got task from broker: myapp.tasks.add[e080e047-b2a2-43a7-af74-d7d9d98b02fc]
    [2014-10-07 08:47:08,299: INFO/MainProcess] Task myapp.tasks.add[e080e047-b2a2-43a7-af74-d7d9d98b02fc] succeeded in 0.183349132538s: 4

9. 另一個例子

下面我們來看一個更爲真實的例子, 在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'], ...)

10. 調試

由於Celery的運行需要啓動多個部件, 我們可能會漏掉一兩個. 所以我們建議:

  • 使用最簡單的設置
  • 使用python debug和logging功能顯示當前的進程

11. Eager模式

如果在settings.py設置:

    CELERY_ALWAYS_EAGER = True

那麼Celery便以eager模式運行, 則task便不需要加delay運行:

    # 若啓用eager模式, 則以下兩行代碼相同
    add.delay(2, 2)
    add(2, 2)

12. 查看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)

13. 檢查結果

每次運行異步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"

14. 定期任務

還有一種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中
  • Execution Options: 更高級的設置, 在此不詳細說明, 請查看celery官方文檔

15. 注意

本篇博文中所描述的方法只適用於開發環境, 而不應當應用於部署環境.

如果希望在部署環境中使用, 最重要的便是使用更穩定和可擴展的broker, 而不是使用kombu.transport.django.


原文鏈接: http://www.weiguda.com/blog/73/

發佈了24 篇原創文章 · 獲贊 4 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章