1 简介
Celery 是一个简单,灵活且可靠的分布式系统,可以处理大量消息,同时为操作提供维护该系统所需的工具。这是一个任务队列,着重于实时处理,同时还支持任务调度。
Celery 通过消息进行通信,通常使用Broker(经纪人)在 clients 和 workers 之间进行调解。要启动一个任务,客户端会在队列中放入一条消息,然后经纪人将消息传递给工人。
一个 Celery 系统可以由多个 worker 和 broker 组成,从而实现高可用性和横向扩展。
Celery 是用 Python 编写的,但协议可以用任何语言实现。除了 Python 之外,还有 Node.js 的 Node-celery,PHP 客户端,golang 的 gocelery 和 Rust 的 rusty-celery。
2 安装配置
2.1 pip安装包
pip install celery==5.2.0
pip install redis==4.3.4
pip install Django==3.2.9
# Celery4 之后Windows上要安装这个工具
pip install eventlet==0.32.0
pip install django-celery-beat==2.2.1
pip install django-celery-results==2.2.0
# 查看celery进程的监控插件
pip install flower==1.0.0
2.2 安装Redis
根据自己系统安装相应的redis
3 创建Django项目
3.1 创建项目
# 创建Django工程
django-admin startproject CeleryTest
# 创建app
cd CeleryTest
django-admin startapp app01
3.2 创建site_celery
文件夹
并在创建相应文件CeleryTest/site_celery/config.py,CeleryTest/site_celery/main.py
在site_celery
中再创建相应任务文件夹btest
和mots
,在文件夹中创建tasks.py
和__init__.py
文件。这里的文件名必须是tasks.py
不能更改
最后的结构图如下
E:.
│ db.sqlite3
│ manage.py
│
├───app01
│ admin.py
│ apps.py
│ models.py
│ tasks.py
│ tests.py
│ urls.py
│ views.py
│ __init__.py
│
│
├───CeleryTest
│ asgi.py
│ settings.py
│ urls.py
│ wsgi.py
│ __init__.py
│
│
└───site_celery
│ config.py
│ main.py
│ __init__.py
│
├───btest
│ tasks.py
│ __init__.py
│
│
├───mots
tasks.py
__init__.py
3.3 配置路由和settings.py
文件
CeleryTest/sttings.py
文件最后配置如下:
ALLOWED_HOSTS = ['*']
# Application definition
INSTALLED_APPS = [
'simpleui',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django_celery_beat',
'django_celery_results',
'app01',
]
# Internationalization
# https://docs.djangoproject.com/en/3.2/topics/i18n/
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
USE_L10N = True
USE_TZ = False
CeleryTest/urls.py
文件最后配置如下:
from django.contrib import admin
from django.urls import path,re_path,include
urlpatterns = [
path('admin/', admin.site.urls),
re_path('app01/', include('app01.urls')),
]
3.4 app01
下的配置
app01/urls.py
from django.urls import path, re_path
from .views import test_celery
urlpatterns = [
re_path(r'^test/$', test_celery, name='test_celery')
]
app01/views.py
from django.shortcuts import render
from site_celery.mots.tasks import add
from site_celery.btest.tasks import mul
from django.http import HttpResponse
# Create your views here.
# def test_celery(request):
# add.delay(3, 5)
# return HttpResponse("Celery works")
def test_celery(request):
result_mots = add.apply_async(args=[3, 5], queue="mots_task")
result_btest = mul.apply_async(args=[3, 5], queue="btest_task")
print(result_mots.id)
print(result_btest.id)
print(result_mots.status)
print(result_btest.status)
return HttpResponse("Celery works")
# return HttpResponse(result.task_id + ":" + result.status)
4 Django-Celery的相关配置
4.1 site_celery/main.py
主执行文件
from celery import Celery
import os
import django
# 把celery和django进行组合,识别和加载django的配置文件
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'CeleryTest.settings')
django.setup()
# 创建celery实例对象
app = Celery("siteCelery")
# 通过app对象加载配置
app.config_from_object("site_celery.config")
# app.config_from_object("django.conf:settings")
# 加载任务
# 参数必须必须是一个列表,里面的每一个任务都是任务的路径名称
# app.autodiscover_tasks(["任务1","任务2"])
app.autodiscover_tasks(["site_celery.mots", "site_celery.btest"])
# 启动Celery的命令
# 切换目录到mycelery根目录下启动
# celery -A mycelery.main worker --loglevel=info
4.2 site_celery/config.py
Celery的配置文件
from celery.schedules import crontab
import datetime
from kombu import Exchange, Queue
# celery 配置
# CELERY_BROKER_URL = 'redis://127.0.0.1:6379/0'
BROKER_URL = 'redis://127.0.0.1:6379/1'
CELERY_TIMEZONE = 'Asia/Shanghai'
DJANGO_CELERY_BEAT_TZ_AWARE = False
CELERYD_CONCURRENCY = 20 # 并发worker数
CELERYD_MAX_TASKS_PER_CHILD = 100 # 每个worker最多执行万100个任务就会被销毁,可防止内存泄露
CELERYD_TASK_TIME_LIMIT = 60 # 单个任务的运行时间不超过此值,否则会被SIGKILL 信号杀死
# CELERY_TASK_ALWAYS_EAGER = True
CELERYD_FORCE_EXECV = True # 非常重要,有些情况下可以防止死锁
# CELERY_CACHE_BACKEND = 'default'
# 支持数据库django-db和缓存django-cache存储任务状态及结果
# 建议选django-db
CELERY_RESULT_BACKEND = "django-db"
# celery内容等消息的格式设置,默认json
CELERY_ACCEPT_CONTENT = ['application/json', ]
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'
ENABLE_UTC = False
CELERY_BEAT_SCHEDULER = 'django_celery_beat.schedulers:DatabaseScheduler'
# 定时任务配置如下
# CELERY_BEAT_SCHEDULER = {
# 'beat_task1': {
# 'task': 'schedule_add',
# 'schedule': datetime.timedelta(seconds=10),
# 'args': (2, 8)
# },
# # 'beat_task2': {
# # 'task': 'mots_add',
# # 'schedule': crontab(hour=4, minute=25),
# # 'args': [4, 5]
# # }
# }
# 定义celery各个队列的名称
CELERY_QUEUES = (
Queue("default", Exchange("default"), routing_key="default"),
Queue("mots_task", Exchange("mots_task"), routing_key="task_mots"),
Queue("btest_task", Exchange("btest_task"), routing_key="task_btest")
)
# 注意: 使用 redis 作为 broker 时, 队列名称,Exchange名称,queue名称 必须保持一致
CELERY_ROUTES = {
"*": {"queue": "default", "routing_key": "default"},
"tasks*": {"queue": "mots_task", "routing_key": "task_mots"},
"task2": {"queue": "btest_task", "routing_key": "task_btest"},
}
# 注意: 使用 redis 作为 broker 时, 队列名称,Exchenge名称,queue名称 必须保持一致
"""
这里只定义了两个队列
mots_task: 用来存放需要优先执行的重要任务, 如果队列仍然存在堵塞的情况, 可以根据更小颗粒度划分出更多的队列
btest_task: 用来存放执行级别较低的任务, 该类型的任务可以允许存在较长时间的延迟
进入manage.py 文件所在目录下, 执行以下命令, 开启worker, 因为我使用了django-celery模块,
所以可以使用manage.py 入口文件进行启动: -Q queue_name 指定队列名称
如果需要后台运行, 可在命令的最后加上 "&", 如果使用supervisor进行进程管理, 则不可以加上 "&", docker部署请自行参考docker 官方文档对 dockerfile 使用方式的说明.
注意: 这里添加了一个使用 celery 队列的worker, 因为在进行任务发送时, 如果没有指明队列, 将默认发送至队列名称为celery的队列中.
# -A 表示 应用目录 这里是celery_tasks.main
# -B 表示 定时任务
# -l 表示日志级别 这是英文小写l不是数字1
# -n woker名 自定义
# -Q 队列名
# .%h 对应不同主机ip 如果默认localhost,所以可以省略.%h
启动命令
Celery -A site_celery.main worker -l info -n workerA.%h -P eventlet -Q mots_task
Celery -A site_celery.main worker -l info -n workerB.%h -P eventlet -Q btest_task
Celery -A site_celery.main worker -l info -n workerB.%h -P eventlet --pool=solo
# 启动定时任务
# 当任务没有指定queue 则任务会加入default 队列 beat(定时任务也会加入default队列)
# celery beat -A site_celery.main -l INFO
# Celery -A site_celery.main beat -l info -f logging/schedule_tasks.log --detach
# --detach: 后台运行
# -f logging/schedule_tasks.log : 后台输出路径
# --scheduler : 指定获取定时任务的方式,这里是从后台数据库中获取
# Celery -A site_celery.main beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler -f logging/schedule_tasks.log --detach
"""
"""
from .tasks import send_emailMes_task
send_emailMes_task.apply_async((params_1, params_2), {"params_3_key": params_3_value}, queue="import_task")
需要注意的是:
使用异步任务对象下的apply_async(), 而不是delay(), 后者无法指定队列名称
参数: (params_1, params_2), 使用这样的方式传递实参, 需要使用*agrs接收
参数: {"active_token":token}, 使用这样的方式传递命名参数, 需要使用**kwagrs接收
参数: queue, 指定将任务发送至那个队列
五.完成以上操作以后就可以进行程序的执行了.
"""
参数说明:
BROKER_URL
: Broker路径,可以连接Redis或者是MQCELERY_TIMEZONE
: CELERY的时区,后期定时任务和这个相关,建议与Django系统相同CELERY_RESULT_BACKEND
: Celery运行结果存储方式,可以是django-db
中,也可以是存放在redis
中,比如(redis://127.0.0.1:6379/2)。支持数据库django-db和缓存django-cache存储任务状态及结果,建议选django-dbCELERY_ACCEPT_CONTENT
: 设置Celery头信息中,接受的数据格式,要与其他两个参数想对应(默认是json格式)CELERY_TASK_SERIALIZER
: 任务数据格式(默认是json格式)CELERY_RESULT_SERIALIZER
: 结果数据格式(默认是json格式)CELERY_BEAT_SCHEDULER
: 定时任务的执行方式,上面例子是使用django_celery_beat
,并通过django进行添加定时任务CELERYD_CONCURRENCY
: 并发worker数CELERYD_MAX_TASKS_PER_CHILD
: 每个worker最多执行完100个任务就会被销毁,可防止内存泄露CELERYD_TASK_TIME_LIMIT
: 单个任务的运行时间不超过此值,否则会被SIGKILL 信号杀死CELERY_TASK_ALWAYS_EAGER
: 如果是这样True,所有任务将通过阻塞在本地执行,直到任务返回CELERYD_FORCE_EXECV
: 非常重要,有些情况下可以防止死锁CELERY_CACHE_BACKEND
: 'default'CELERY_MESSAGE_COMPRESSION
: 压缩方案的选择,zlib, bzip2,默认是发送没有压缩的数据CELERYD_PREFETCH_MULTIPLIER
:celery worker 每次去BROKER中预取任务的数量CELERY_ENABLE_UTC
:如果消息中的已启用日期和时间将转换为使用UTC时区 (False or True)CELERY_TASK_RESULT_EXPIRES
:结果过期时间(60 * 60 * 24)CELERY_QUEUES
:队列设置CELERY_ROUTES
:路由设置,那个任务放入那个队列
4.2.1 CELERY_QUEUES和CELERY_ROUTES说明
下面是实际配置
# 定义celery各个队列的名称
CELERY_QUEUES = (
Queue("default", Exchange("default"), routing_key="default"),
Queue("mots_task", Exchange("mots_task"), routing_key="task_mots"),
Queue("btest_task", Exchange("btest_task"), routing_key="task_btest")
)
# 注意: 使用 redis 作为 broker 时, 队列名称,Exchange名称,queue名称 必须保持一致
CELERY_ROUTES = {
"*": {"queue": "default", "routing_key": "default"},
"tasks*": {"queue": "mots_task", "routing_key": "task_mots"},
"task2": {"queue": "btest_task", "routing_key": "task_btest"}
}
从上面可以看到Queue后面共有三个参数:
- mots_task:这个是队列的名称
- Exchange("mots_task"):交换器,将生产者接收到的信息路由到Queue中。所以这个名称应该是第一个队列名称
- routing_key="task_mots":路由的key用来在下面的队列路由中进行指定匹配
注意:
- mots_task 队列的worker,不会处理 bteast_task的任务。但是default队列的worker会处理所有队列的任务,但是会在其他队列都坏了的情况下。
- worker启动时,不指定队列时(不加-Q)时,那么久莫属于这个队列,即可以为所有队列提供worker服务。默认队列名称为
celery
CELERY_ROUTES配置如下:
- task2:路由匹配的的任务名称
- queue:路由对应的队列名称
- routing_key:与队列中的routing_key参数一致
一旦配置了route后,所有的任务名都必须要指定route,否则无法执行。并且route匹配是长匹配规则。
注意:
tasks*
:匹配的是以task开头的任务task2
:是精确匹配task2名称的任务,让其走这条路由。*
:其他任务名称匹配这条路由。如果以上的队列worker服务器都坏了,这些任务全部被放到这个队列里,该队列的worker将继续处理这些任务。所以"*": {"queue": "default", "routing_key": "default"},
这条队列必须配置
5 Celery任务编写
5.1 创建数据库表
因为配置中结果是放在db中的,所以要创建表
python manager.py makemigrations
python manager.py migrate
# 创建超级管理员
django-admin createsuperuser
5.2 创建任务
相关文章可以参考:Celery任务讲解
任务都要在site_celery
文件夹下创建,先创建一个python包(文件夹中包含__init__.py
文件),比如例子中就创建了mots
和btest
两个文件夹
之后要在其中创建一个/mots/tasks.py
文件,名称必须是**tasks.py
**。这样系统会自动找到该文件并加载,否则无法识别。
/mots/tasks.py
from site_celery.main import app
import time
# @app.task(name='task1', time_limit=10)
# time_limit:设置任务运行时长,超过这个时间任务没结束,进程会被杀掉重启,然后跳过这个任务执行下一个
@app.task(name='task1')
def add(x, y):
time.sleep(2)
print("The mots_add task has been run , result is : %s !" % str(x + y))
return x + y
/btest/tasks.py
from site_celery.main import app
from celery import shared_task
import time
@app.task(name='task2')
def mul(x, y):
time.sleep(2)
print("The btest_mul task has been run , result is : %s !" % str(x * y))
return x * y
# 做定时任务
@app.task(name='schedule_add')
def share_add(x, y):
time.sleep(2)
print("--------------------------定时任务运行---------------------------------")
print("The share_add task has been run , result is : %s !" % str(x + y))
return x + y
-
shared_task
:@shared_task方式可以让该方法变成一个共享的方法,不绑定在app上 -
@app.task
:绑定在创建的实例上的注册- name:该参数是命名task的名字,可以和后面的路由相匹配
- time_limit:设置任务运行时长,超过这个时间任务没结束,进程会被杀掉重启,然后跳过这个任务执行下一个
- bind:是否和函数绑定,选择
True
或者False
5.3 调用任务
app01/views.py
from django.shortcuts import render
from site_celery.mots.tasks import add
from site_celery.btest.tasks import mul
from django.http import HttpResponse
# Create your views here.
# def test_celery(request):
# add.delay(3, 5)
# return HttpResponse("Celery works")
def test_celery(request):
result_mots = add.apply_async(args=[3, 5], queue="mots_task")
result_btest = mul.apply_async(args=[3, 5], queue="btest_task")
print(result_mots.id)
print(result_btest.id)
print(result_mots.status)
print(result_btest.status)
# return HttpResponse("Celery works")
return HttpResponse(result_mots.id + ":" + result_mots.status)
5.3.1 delay调用方法
可以通过方法名.delay(参数1,参数2)
的方式进行传参,但是该方法无法指定使用哪个队列
5.3.2 apply_async()调用方法
该方法可以调用指定队列进行工作
result_mots = add.apply_async(args=[3, 5], queue="mots_task")
- 参数: (params_1, params_2), 使用这样的方式传递实参, 需要使用*agrs接收
- 参数: {"active_token":token}, 使用这样的方式传递命名参数, 需要使用**kwagrs接收
- 参数: queue, 指定将任务发送至那个队列
5.3.3 Celery的内置状态
更多Celery相关操作可以查看:Celery中文手册
PENDING
:任务正在等待执行或未知。任何未知的任务 ID 都默认处于挂起状态。STARTED
:任务已经开始。默认情况下不会记录,需要启用,请参阅app.Task.track_started.
。meta-data:正在执行任务的职程(Worker) pid 和主机名。SUCCESS
:任务执行成功。meta-data:任务结果返回值 propagates:Yes ready: YesFAILURE
:任务执行失败。meta-data:执行异常时的任务信息,其中 traceback 包含引发错误的堆栈信息。 propagates:YesRETRY
:任务处于重试状态。meta-data:结果信息包含导致重试的异常信息,traceback 包含引发异常时堆栈的回溯。 propagates:NoREVOKED
:任务被撤销。propagates:Yes
5.3.4 Django Admin 添加周期性任务
先要将参数设置如下
CELERY_BEAT_SCHEDULER = 'django_celery_beat.schedulers:DatabaseScheduler'
之后启动Django,并进入admin中。Periodic tasks是定时任务模块
配置好之后:
6 Celery启动
要在项目更目录下执行下面的命令
6.1 启动命令
启动一个worker,并指明队列
Celery -A site_celery.main worker -l info -n workerA.%h -P eventlet -Q mots_task
启动worker,不指定队列
Celery -A site_celery.main worker -l info -n workerB.%h -P eventlet
启动定时任务
# --detach : 后台运行
# --scheduler : 通过后台数据库进行数据获取
# 通过后台数据库进行定时任务执行
Celery -A site_celery.main beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler -f logging/schedule_tasks.log --detach
所有命令
# 启动 workerA 并指定 mots_task 为队列
Celery -A site_celery.main worker -l info -n workerA.%h -P eventlet -Q mots_task
# 启动 workerB 并指定 btest_task 为队列
Celery -A site_celery.main worker -l info -n workerB.%h -P eventlet -Q btest_task
# 后台运行方法
Celery multi start logging/celery.log -A site_celery.main -l info -n workerC.%h -P eventlet -Q btest_task
# 启动定时任务
# 当任务没有指定queue 则任务会加入default 队列 beat(定时任务也会加入default队列)
# 后台运行BEAT
Celery -A site_celery.main beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler -f logging/schedule_tasks.log --detach
6.2 启动参数
-A
: 表示应用目录 这里是celery_tasks.main-B
: 表示定时任务-l
: 表示日志级别 这是英文小写l不是数字1-n
: woker名 自定义-Q
: 队列名.%h
: 对应不同主机ip 如果默认localhost,所以可以省略.%h-P
:在windows上启动,并且celery版本在4.0以上时需要该参数