Django Celery 使用

689 阅读11分钟

1 简介

Celery 是一个简单,灵活且可靠的分布式系统,可以处理大量消息,同时为操作提供维护该系统所需的工具。这是一个任务队列,着重于实时处理,同时还支持任务调度。

Celery 通过消息进行通信,通常使用Broker(经纪人)在 clients 和 workers 之间进行调解。要启动一个任务,客户端会在队列中放入一条消息,然后经纪人将消息传递给工人。

一个 Celery 系统可以由多个 worker 和 broker 组成,从而实现高可用性和横向扩展。

Celery 是用 Python 编写的,但协议可以用任何语言实现。除了 Python 之外,还有 Node.js 的 Node-celery,PHP 客户端,golang 的 gocelery 和 Rust 的 rusty-celery。

celery-3.jpeg

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中再创建相应任务文件夹btestmots,在文件夹中创建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 definitionINSTALLED_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 = TrueUSE_L10N = TrueUSE_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.pyCelery的配置文件

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或者是MQ
  • CELERY_TIMEZONE: CELERY的时区,后期定时任务和这个相关,建议与Django系统相同
  • CELERY_RESULT_BACKEND: Celery运行结果存储方式,可以是django-db中,也可以是存放在redis中,比如(redis://127.0.0.1:6379/2)。支持数据库django-db和缓存django-cache存储任务状态及结果,建议选django-db
  • CELERY_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后面共有三个参数:

  1. mots_task:这个是队列的名称
  2. Exchange("mots_task"):交换器,将生产者接收到的信息路由到Queue中。所以这个名称应该是第一个队列名称
  3. routing_key="task_mots":路由的key用来在下面的队列路由中进行指定匹配

注意:

  • mots_task 队列的worker,不会处理 bteast_task的任务。但是default队列的worker会处理所有队列的任务,但是会在其他队列都坏了的情况下。
  • worker启动时,不指定队列时(不加-Q)时,那么久莫属于这个队列,即可以为所有队列提供worker服务。默认队列名称为celery

CELERY_ROUTES配置如下:

  1. task2:路由匹配的的任务名称
  2. queue:路由对应的队列名称
  3. 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文件),比如例子中就创建了motsbtest两个文件夹

之后要在其中创建一个/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: Yes
  • FAILURE:任务执行失败。meta-data:执行异常时的任务信息,其中 traceback 包含引发错误的堆栈信息。 propagates:Yes
  • RETRY:任务处于重试状态。meta-data:结果信息包含导致重试的异常信息,traceback 包含引发异常时堆栈的回溯。 propagates:No
  • REVOKED:任务被撤销。propagates:Yes

5.3.4 Django Admin 添加周期性任务

先要将参数设置如下

 CELERY_BEAT_SCHEDULER = 'django_celery_beat.schedulers:DatabaseScheduler'

之后启动Django,并进入admin中。Periodic tasks是定时任务模块

celery-4.png

配置好之后:

celery-5.png

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以上时需要该参数