參考
官方文檔Celery在Django上
官方文檔Celery
Celery中文文檔
Github地址
Celery全面學習筆記
分佈式任務隊列Celery入門與進階
一、概要
Celery是由Python開發、簡單、靈活、可靠的分佈式任務隊列,其本質是生產者消費者模型,生產者發送任務到消息隊列,消費者負責處理任務。
Celery側重於實時操作,但對調度支持也很好,其每天可以處理數以百萬計的任務。特點:
- 簡單:熟悉celery的工作流程後,配置使用簡單
- 高可用:當任務執行失敗或執行過程中發生連接中斷,celery會自動嘗試重新執行任務
- 快速:一個單進程的celery每分鐘可處理上百萬個任務
- 靈活:幾乎celery的各個組件都可以被擴展及自定製
應用場景舉例:
- web應用:當用戶在網站進行某個操作需要很長時間完成時,我們可以將這種操作交給Celery執行,直接返回給用戶,等到Celery執行完成以後通知用戶,大大提好網站的併發以及用戶的體驗感。
- 任務場景:比如在運維場景下需要批量在幾百臺機器執行某些命令或者任務,此時Celery可以輕鬆搞定。
- 定時任務:向定時導數據報表、定時發送通知類似場景,雖然Linux的計劃任務可以幫我實現,但是非常不利於管理,而Celery可以提供管理接口和豐富的API。
Celery由以下四部分構成:任務模塊(Task)、消息中間件(Broker)、任務執行單元Worker、結果存儲(Backend)
- 任務模塊Task:包含異步任務和定時任務,異步任務通常在業務邏輯中被觸發併發往任務隊列,而定時任務由Celery Beat進程週期性的將任務發往任務隊列
- 消息中間件Broker:即爲任務調度隊列,接收任務生產者發來的消息(即任務),將任務存入隊列。Celery本身不提供隊列服務,官方提供了很多備選方案,支持RabbitMQ、Redis、Amazon SQS等,官方推薦RabbitMQ。
- 任務執行單元Worker:Worker是任務執行單元,負責從消息隊列中取出任務執行,它可以啓動一個或者多個,也可以啓動在不同的機器節點,這就是其實現分佈式的核心。實時監控消息隊列,獲取隊列中調度的任務並執行它。
- 結果存儲Backend:用於存儲任務的執行結果。官方提供了諸多的存儲方式支持:RabbitMQ、 Redis、Memcached、SQLAlchemy、Django ORM、Apache Cassandra、Elasticsearch
工作原理:
- 任務模塊Task包含異步任務和定時任務。其中,異步任務通常在業務邏輯中被觸發併發往消息隊列,而定時任務由Celery Beat進程週期性地將任務發往消息隊列;
- 任務執行單元Worker實時監視消息隊列獲取隊列中的任務執行;
- Woker執行完任務後將結果保存在Backend中;
二、環境
- Python:3.7
- Django:2.2.4
- Celery:4.3
注意:Celery 4.x和3.x版本相差很大,建議使用4.x,另外,如果是Django 1.8之前的版本,只能使用Celery 3.x
三、依賴
需要安裝的依賴有:
pip install celery
pip install redis
# 或者是直接:pip install "celery[redis]"
pip install mysqlclient
pip install django-celery-results
以上的依賴中,只有Celery是必須的,其他的依賴都不是必須,如下:
- redis:非必須,默認Celery是使用RabbitMQ作爲Broker中間人的並且默認安裝好RabbitMQ依賴了,我自己習慣用Redis,所以需要安裝Redis依賴
- mysqlclient:非必須,默認Celery是不保存任務結果的,如果想要查看任務的結果並且可以保存到數據庫中,所以必須安裝mysqlclient連接數據庫
- django-celery-results:非必須,默認Celery是不保存任務結果的,如果想要查看任務的結果並且保存到數據庫中,就必須安裝該依賴,如果不想保存到數據庫的話,也可以使用Redis來進行保存
中間人:
- RabbitMQ:默認
- Redis
- Amazon SQS
注意:Celery之前的版本是支持以數據庫作爲中間人的,但是4.x不支持數據庫作爲中間人,具體可以參考 官方文檔
四、初始化
1. 新建一個應用
新建一個應用,應用名隨意,這裏的應用名爲tasks
python manage startapp tasks
setting.py中增加應用:
INSTALLED_APPS = (
...,
'tasks',
'django_celery_results',
)
執行遷移
python manage.py migrate django_celery_results
2. setting.py同級目錄修改init.py
setting.py同級目錄修改__init__.py:
from __future__ import absolute_import, unicode_literals
# This will make sure the app is always imported when
# Django starts so that shared_task will use this app.
from .celery import app as celery_app
__all__ = ('celery_app',)
3. setting.py同級目錄新建celery.py
setting.py同級目錄新建celery.py:
from __future__ import absolute_import, unicode_literals
import os
from celery import Celery
from django.conf import settings
# 獲取當前文件夾名,即爲該Django的項目名
project_name = os.path.split(os.path.abspath('.'))[-1]
project_settings = '%s.settings' % project_name
# 設置環境變量
os.environ.setdefault('DJANGO_SETTINGS_MODULE', project_settings)
# 實例化Celery
app = Celery(project_name)
# 使用Django的settings文件配置celery
app.config_from_object('django.conf:settings', namespace='CELERY')
# Celery加載所有註冊的應用
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)
@app.task(bind=True)
def debug_task(self):
print('Request: {0!r}'.format(self.request))
注意:設置環境變量的時候
os.environ.setdefault('DJANGO_SETTINGS_MODULE', project_settings)
project_settings一定是項目名.settings,如果項目名和目錄名不一樣的話,則不能使用上面的方式來獲取當前文件夾名字了
4. 修改setting.py
setting.py中最下面新增:
# Broker的地址
CELERY_BROKER_URL = 'redis://127.0.0.1:6379'
# 任務執行返回結果
CELERY_RESULT_BACKEND = 'django-db'
# celery內容等消息的格式設置
CELERY_ACCEPT_CONTENT = ['application/json', ]
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'
# celery時區設置,使用settings中TIME_ZONE同樣的時區
CELERY_TIMEZONE = TIME_ZONE
更多的配置可以參考 官方文檔
Redis中間人的配置格式爲:
redis://:password@hostname:port/db_number
默認使用的是 localhost 的 6379 端口中 0 數據庫
Celery默認是不保存任務的結果,但是支持保存任務的功能,內置一些任務的後端結果存儲:
- 數據庫:如果想要使用數據庫的話,需要安裝django-celery-results,並且CELERY_RESULT_BACKEND需要設置爲:django-db,默認存在 django_celery_results_taskresult 表裏面
- Memcached
- RPC(RabbitMQ/AMQP)
- Redis:格式跟Redis中間人的配置格式一致,使用Redis的話不需要安裝django-celery-results
五、測試
1. 新增任務
在tasks應用下新增tasks.py:
from __future__ import absolute_import, unicode_literals
from celery import shared_task
import datetime
@shared_task
def add(x, y):
res = x + y
time_format = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
print('當前時間爲:' + time_format + ' ,兩個數相加的結果爲:')
print(res)
return res
@shared_task
def mul(x, y):
res = x * y
time_format = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
print('當前時間爲:' + time_format + ' ,兩個數相乘的結果爲:')
print(res)
return res
@shared_task
def xsum(numbers):
res = sum(numbers)
print(res)
return res
最終的目錄結構如下:
項目名
│
├─manage.py
│
├─tasks
│ │ admin.py
│ │ apps.py
│ │ models.py
│ │ tests.py
│ │ views.py
| | tasks.py
│ └ __init__.py
│
├─templates
│
└─項目名
│ settings.py
│ urls.py
│ wsgi.py
| celery.py
└ __init__.py
2. 啓動Worker
在命令行下執行:
celery -A 項目名 worker -l info
# 或者是
celery -A 項目名 worker --loglevel=info
注意:如果是在Windows 10下,並且是Celery 4.x,請使用下面的命令啓動,否則在測試的時候會報錯,具體見最下面的問題集。
celery -A 項目名 worker --pool=solo -l info
3. 測試
創建一個異步任務來測試,在命令行下執行:
python manage.py shell
from tasks.tasks import add, mul, xsum
res = add.delay(2,3)
res.get()
看看有沒有結果輸出,或者是可以在Admin管理頁面的Celery Results裏面看到結果
delay() 是 apply_async() 的快捷方法,即執行異步任務,異步任務由Worker來進行處理,可以通過控制檯輸出的日誌進行查看執行情況。
調用任務會返回一個 AsyncResult 的實例,用於檢測任務的狀態,等待任務完成獲取返回值(如果任務執行失敗,會拋出異常)。
其他的命令還有:
from tasks.tasks import add, mul, xsum
# 調用異步任務
t = add.delay(2, 3)
# 同步拿結果
t.get()
# 同步拿結果,並且設置獲取結果的超時時間,超過1秒拿不到結果則報錯
t.get(timeout=1)
# 檢查任務是否完成
t.ready()
# 如果出錯,獲取錯誤結果,不觸發異常
t.get(propagate=False)
t.traceback
更多的命令可以查看 官方文檔
六、定時任務
1. 概要
在Django中實現定時任務,基本上有兩套方案:
- Celery原生:比較簡單,在配置中使用CELERY_BEAT_SCHEDULE來人爲加載定時任務
- 後臺管理任務:稍微複雜一點,安裝django-celery-beat,在Admin後臺中管理和配置定時任務
注意:Celery 4.x和3.x的定時任務不兼容,建議使用4.x
2.Celery原生
使用Celery原生方案很簡單, 不需要安裝其他的依賴,只需要在配置中配置CELERY_BEAT_SCHEDULE即可
配置
在setting.py最後面加上:
from celery.schedules import crontab
from datetime import timedelta
# 定時任務
CELERY_BEAT_SCHEDULE = {
# 每十秒執行一次add方法
'add-every-10-seconds': {
'task': 'tasks.tasks.add',
# 多長時間執行一次
'schedule': 10.0, # 支持直接用數字表示秒數
# 'schedule': timedelta(seconds=10), # 可以用timedelta對象
# 必要的參數,這裏指add()的參數
'args': (16, 26)
},
# 每個週一的20:57分執行一次mul方法
'add-every-monday-morning': {
'task': 'tasks.tasks.mul',
'schedule': crontab(hour=20, minute=57, day_of_week=1),
'args': (16, 16),
},
}
更多的用法可以參考 官方文檔
啓動
命令行上執行:
# 啓動worker
celery -A 項目名 worker --pool=solo -l info
# 啓動定時任務beat
celery -A 項目名 beat -l info
即可在命令行上看到輸出結果,或者是可以在Admin管理頁面的Celery Results裏面看到結果
3. 後臺管理任務
使用後臺管理任務的話,可以更方便的使用Django Admin進行所有定時任務的管理以及查看每次任務的結果,而且把任務存到Django的數據庫裏面去,不過相比起原生方案,步驟比較多且麻煩,而且需要安裝django-celery-beat,django-celery-beat會在admin後臺生成定時任務模塊進行管理,模塊爲:PERIODIC TASKS,一般爲Periodic tasks,保存在django_celery_beat_periodictask 表
配置
安裝django-celery-beat
pip install django-celery-beat
setting.py中增加應用:
INSTALLED_APPS = (
...,
'django_celery_beat',
)
執行遷移,會自動創建一些表:
python manage.py migrate django_celery_beat
登錄Django Admin後,可以看到多了PERIODIC TASKS模塊,在Periodic tasks裏面配置定時任務即可,配置定時任務很簡單,這裏就跳過了
啓動
命令行上執行:
# 啓動worker
celery -A 項目名 worker --pool=solo -l info
# 啓動定時任務beat
celery -A 項目名 beat -l info -S django
# 或者是
celery -A 項目名 beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler
即可在命令行上看到輸出結果,或者是可以在Admin管理頁面的Celery Results裏面看到結果,並且啓動之後,會在根目錄下生成celerybeat.pid文件
注意:使用django-celery-beat來管理後臺任務的時候,啓動beat需要在後面加上-S參數來指定調度器
注意:這兩個方案也可以同時使用,啓動的時候指定-S參數即可同時使用的,如果一起使用的話,原生設置的定時任務,會被同步到Django Admin後臺裏面的定時任務去
如果想要省事的話,也可以把–scheduler django_celery_beat.schedulers:DatabaseScheduler寫到配置文件setting.py中去,即
# 定時任務來源 從數據庫中讀取
CELERY_BEAT_SCHEDULER = 'django_celery_beat.schedulers:DatabaseScheduler'
然後啓動定時任務的時候,就可以省略後面了,即:
celery -A 項目名 beat -l info
注意:如果setting.py中的CELERY_TIMEZONE設置的是非UTC的話,比如說’Asia/Shanghai’,且USE_TZ設置爲False的話,那麼運行定時任務的時候會報錯,這個時候需要在配置中加上以下設置可以避免報錯,具體的報錯可以參考最下面的問題集。
# 避免時區的問題
CELERY_ENABLE_UTC = False
DJANGO_CELERY_BEAT_TZ_AWARE = False
七、問題集
1. 報錯:ValueError(‘not enough values to unpack (expected 3, got 0)’)
參考:
https://blog.csdn.net/qq_30242609/article/details/79047660
https://segmentfault.com/q/1010000010560344
https://github.com/celery/celery/issues/4178
在Windows平臺下,並且是Celery 4.x,在命令行上執行後Worker可能會有報錯,報錯如下:
Task handler raised error: ValueError('not enough values to unpack(expected 3, got 0)')
Traceback (most recent call last):
File "C:\Users\boli.hong\AppData\Roaming\Python\Python37\site-packages\billiard\pool.py", line 362, in workloop
result = (True, prepare_result(fun(*args, **kwargs)))
File "C:\Users\boli.hong\AppData\Roaming\Python\Python37\site-packages\celery\app\trace.py", line 546, in _fast_trace_task
tasks, accept, hostname = _loc
ValueError: not enough values to unpack (expected 3, got 0)
原因:
看別人描述大概就是說Celery 4.x不支持Windows平臺了,所以Windows下運行就會出現這個問題
解決方法:
- 第一種方法(建議):–pool=solo,只需要啓動Worker的時候,加上參數即可,即celery -A 項目名 worker --pool=solo -l info
- 第二種方法:eventlet,需要先pip install eventlet,然後啓動Worker的時候加上-P參數,celery -A 項目名 worker -l info -P eventlet
2. MySQL backend does not support timezone-aware datetimes when USE_TZ is False.
當setting.py中的CELERY_TIMEZONE設置的是非UTC的話,比如說’Asia/Shanghai’,且USE_TZ設置爲False的話,那麼啓動定時任務的時候會報錯,報錯信息如下:
raise ValueError("MySQL backend does not support timezone-aware datetimes when USE_TZ is False.")
ValueError: MySQL backend does not support timezone-aware datetimes when USE_TZ is False.
解決方法:
- 第一種方法:setting.py中的USE_TZ需要設置爲True(不推薦)
- 第二種方法(推薦):setting.py中增加:
# 避免時區的問題
CELERY_ENABLE_UTC = False
DJANGO_CELERY_BEAT_TZ_AWARE = False