Django中異步任務---django-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中:

第一二項是必須的, 第三項則告訴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:

當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:

如果用gevent需要加參數-P gevent

? ? 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:


9. 另一個例子

下面我們來看一個更為真實的例子, 在views.py和tasks.py中:

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:

13. 檢查結果

每次運行異步task后, Celery都會返回AsyncResult對象作為結果. 你可以將其保存, 然后在將來查看該task是否運行成功和返回結果:


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.

1. 簡單設置

一個簡單的Celery堆有一個queue和一個worker進程組成. 使用以下命令啟動worker:

? ? python manage.py celery worker -B

以上命令是基于django-celery, 當然你也可以celery自身啟動worker. 通常我們使用supervisord管理celery worker的啟動和重啟, 而不是使用手動的方式. supervisord的介紹我們會在今后的文章中作詳細介紹. 現在我們只需要知道它是一款進程管理程序即可. 當然, 你也可以選擇類似的系統, 例如init.d, upstart, runit或god等.

"-B"參數告訴celery在啟動worker時同時啟動celery beat, 并使用統一進程, 以便執行定期任務.

在部署服務器上, 我們使用Redis或RabbitMQ作為broker. 而在這一簡單的celery堆中, 我們用django數據庫儲存執行結果, 或干脆忽略結果都可.

2. 完整設置

如果簡單設置無法滿足我們的需要的話, 我們只需要做一些簡單的改變就能完整設置Celery異步任務. 完整設置中, 我們使用多個queue來區分任務優先級. 每個queue我們配置一個不同concurrency設置的worker. beat進程也與worker進程分離出來.

? ? # 默認 queue? ? python manage.py celery worker -Q celery

? ? # 高優先級 queue. 10個 workers? ? python manage.py celery worker -Q high -c 10? ? # 低優先級 queue. 2個 workers? ? python manage.py celery worker -Q low -c 2? ? # Beat 進程? ? python manage.py celery beat

注意, 其中high和low只是queue的名字, 并沒有其他特殊意義. 我們通過為高優先級的queue配置高concurrency的worker, 使高優先級queue能夠使用更多的資源.

同樣的, 這些啟動命令通過supervisor管理. 至于broker, 我們還是使用Redis或RabbitMQ. 而任務結果則可以儲存在Redis或Memcached這些擁有高寫入速度的系統中. 如果有必要, 這些worker進程可以移到其他服務器中, 但最好共享一個broker和結果儲存系統.

3. 擴展性

我們不能一味的依靠增加額外的worker來提高性能, 因為每個worker都會占用一定的資源. 默認的concurrency設置是, 都多少CPU便創建多少worker, 并為每個worker創建一個新的進程. 將concurrency設置的太高則會很快的榨干服務器的CPU和內存資源.

對于I/O資源需求較大的任務, 我們則可以指定worker使用gevent或eventlet池, 而不是使用更多進程. 這一配置使用的內存資源會大大降低, 同時提升concurrency的性能. 需要注意的是, 但如果我們涉及到的library沒有為greenlet打過補丁的話, 很有可能會阻塞所有的任務!

4. 注意

還有需要注意的是django的transaction. transaction根據django的版本和是否已web request形式傳入有所不同, 所以你需要自己查閱相關的文檔.

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,002評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,400評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,136評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,714評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,452評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,818評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,812評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,997評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,552評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,292評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,510評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,035評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,721評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,121評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,429評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,235評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,480評論 2 379