django+celery worker+celery beat异步任务+定时任务的使用
简介
Celery是一个功能完备即插即用的异步任务队列系统。它适用于异步处理问题。例如,当执行一些比较耗时的操作,例如发送邮件、或者文件上传, 图像处理等等时,我们可将其异步执行,这样用户不需要等待很久,提高用户体验。
celery优点:
简单,易于使用和维护,有丰富的文档。
高效,单个celery进程每分钟可以处理数百万个任务。
灵活,celery中几乎每个部分都可以自定义扩展。
celery执行任务流程:
图片来源:https://blog.csdn.net/qq_52385631/article/details/122783121
安装
我本机相关库版本:
celery 4.4.0
Django 3.2.20
django-celery-beat 2.2.0
django-celery-results 1.1.2
django-redis 4.10.0
eventlet 0.33.3
redis 3.3.8
celery
安装命令:pip install -U celery
( -U是update的意思,有就进行更新,没有就安装)
关于版本选择:
Python 3.7: Celery 5.2 or earlier.
Python 3.6: Celery 5.1 or earlier.
Python 2.7: Celery 4.x series.
Python 2.6: Celery series 3.1 or earlier.
Python 2.5: Celery series 3.0 or earlier.
Python 2.4: Celery series 2.2 or earlier.
redis
Celery需要一种解决消息的发送和接受的方式,我们把这种用来存储消息的的中间装置叫做message broker, 也可叫做消息中间人。RabbitMQ 和 Redis 中间人的消息传输支持所有特性,一般选用这两种方案之一作为broker。
RabbitMQ 和 Redis 的简单对比:
RabbitMQ 和 Redis 都可以做队列,但是他们还是有区别的。比如,Redis的消息队列,如果在从队列pop出去的时候,worker处理失败的话,数据不会回到队列中,需要从业务中手动把失败的处理数据push到队列中;而RabbitMQ可以自动处理失败的worker使数据不丢失;RabbitMQ还可以保证数据在传输过程中持久化,在通道和队列中的数据可以设置为持久化。Redis严格来说并不是消息队列,它是一个内存数据库,不过因为其某些特性适合用来充当队列,所以也多被用于做简单的mq(message queue)
我们这里采用 Redis 做 broker 。
安装命令:pip install redis
django-redis
django-redis 是一个使 Django 支持 Redis cache/session 后端的全功能组件。
安装命令:pip install django-redis
django-celery-beat
用于动态配置定时任务。
安装命令:pip install celery-beat
django-celery-results
是一个方便的插件,可以将 celery 的执行结果保存到 Django 的数据库中,这样一来,我们就可以在任务执行完成后方便地通过模型获取任务的结果。
安装命令:pip install django-celery-results
celery配置
配置celery可以选择两种方式:
-
把有关 celery 相关的配置直接放在 settings.py 中,或者单独创建一个配置文件 celery_settings.py 与 settings.py 同级,然后再在 settings.py 中引入。这里我们使用后者。
# -- celery_settings.py -- # celery 配置 # 为存储结果设置过期日期,默认1天过期。如果beat开启,Celery每天会自动清除。 # 设为0,存储结果永不过期 CELERY_RESULT_EXPIRES = 0 # 使用django数据库(django-db),以后运行worker就会保存到数据库中,可以通过ORM进行访问 # CELERY_RESULT_BACKEND = 'redis://127.0.0.1:6379/1' CELERY_RESULT_BACKEND = 'django-db' # broker 设置 指定中间代理人将任务存到哪里,这里是使用redis的1号库 password改成自己的密码 CELERY_BROKER_URL = 'redis://:password@127.0.0.1:6379/1' # celery 序列化与反序列化配置 CELERY_ACCEPT_CONTENT = ['json'] CELERY_TASK_SERIALIZER = 'json' # 指定时区,默认是 UTC CELERY_TIMEZONE = 'Asia/Shanghai' # 有些情况下可以防止死锁 非常重要! CELERYD_FORCE_EXECV = True # CELERY_TASK_RESULT_EXPIRES = 60*60*24 # 后端存储的任务超过一天时,自动删除数据库中的任务数据,单位秒 CELERY_MAX_TASKS_PER_CHILD = 1000 # 使用django_celery_beat插件用来动态配置任务 # 不建议将定时任务执行配置写在这里 建议使用数据库动态配置 # CELERY_BEAT_SCHEDULE = { # 'celery-beat-task1': { # 本次调度的任务 # 'task': 'task1', # 这里的任务名称必须被注册,如果写了别名,直接写别名就可以了 # 定时任务的调度周期 # 'schedule': crontab(minute='*') # 这里使用了crontab表达式 # 'args': (16, 16), # 任务是一个函数,所以如果有参数则需要传递可以写在这里 }, } # 使用数据库动态配置 CELERY_BEAT_SCHEDULER = 'django_celery_beat.schedulers:DatabaseScheduler' # 避免时区的问题报错进而导致screen窗口关闭 CELERY_ENABLE_UTC = False DJANGO_CELERY_BEAT_TZ_AWARE = False
-
settings.py文件中新增以下内容:
# -- settings.py -- # ... from .celery_settings import * INSTALLED_APPS = [ # ... 'django_celery_results', 'django_celery_beat', # ... ]
新建celery.py
这一步作用是指定django环境、创建Celery app和指定Celery配置文件的启动位置。
import os
from celery import Celery
# 设置django环境
# 把celery和django进行组合,需要识别和加载django的配置文件
os.environ.setdefault('DJANGO_SETTINGS_MODULE', '项目名称.settings.settings')
# 创建celery实例对象
app = Celery('项目名称')
# 使用CELERY_ 作为前缀
app.config_from_object('django.conf:settings', namespace='CELERY')
# 自动发现每个app下的tasks.py
app.autodiscover_tasks()
新建task.py
在创建的app目录下或者根目录下新建一个名为tasks.py的文件,文件名不可更改
from 项目名称 import celery_app
@celery_app.task(name='task1')
def celery_task1(a,b,type='add'):
result = None
if type == 'add':
result = a + b
elif type == 'subtract':
result = a - b
elif type == 'multiply':
result = a * b
elif type == 'divide':
result = a / b
print(result)
装饰器
如果你正在使用 Django,或者你是一个库的作者,那么你可能想要使用shared_task()
装饰器:
from celery import shared_task
@shared_task
def add(x, y):
return x + y
当将多个装饰器与任务装饰器结合使用时,必须确保任务装饰器最后应用(奇怪的是,在Python 中,这意味着它必须位于列表中的第一个):
@app.task
@decorator2
@decorator1
def add(x, y):
return x + y
绑定任务
任务被绑定意味着该任务的第一个参数将始终是任务实例(self
),就像 Python 绑定方法一样:
logger = get_task_logger(__name__)
@app.task(bind=True)
def add(self, x, y):
logger.info(self.request.id)
绑定任务需要用于重试(使用app.Task.retry()
)、访问有关当前任务请求的信息以及添加到自定义任务基类的任何其他功能。
任务继承
任务装饰器的参数base
指定任务的基类:
import celery
class MyTask(celery.Task):
def on_failure(self, exc, task_id, args, kwargs, einfo):
print('{0!r} failed: {1!r}'.format(task_id, exc))
@app.task(base=MyTask)
def add(x, y):
raise KeyError()
名称
每个任务必须有一个唯一的名称。如果没有提供明确的名称,任务装饰器将自动生成一个,并且该名称将基于:
1)定义任务的模块。 2)任务函数的名称。
设置明确名称的示例:
>>> @app.task(name='sum-of-two-numbers')
>>> def add(x, y):
... return x + y
>>> add.name
'sum-of-two-numbers'
最佳做法是使用模块名称作为命名空间,这样如果在另一个模块中已经定义了同名的任务,名称就不会冲突。
>>> @app.task(name='tasks.add')
>>> def add(x, y):
... return x + y
您可以通过调查任务的属性来了解任务的名称.name
:
>>> add.name
'tasks.add'
我们在此处指定的名称 ( tasks.add
) 正是在名为 的模块中定义任务时自动为我们生成的名称tasks.py
:
@app.task
def add(x, y):
return x + y
>>> from tasks import add
>>> add.name
'tasks.add'
迁移数据库
python manage.py migrate
执行完毕后数据库会出现几个django_celery_beat
为前缀的表:
启动定时任务
定时任务是独立于django项目运行的,django只是定时任务的入口和操作数据库的入口。
django-celery-beat的启动分两步,一是工作者单独启动(worker),二是生产者单独启动(beat)。建议先启动 worker 再启动 beat 。打开两个新的命令行窗口,进入django项目的根目录(与 manage.py 同级目录)下分别依次执行:
(1)启动celery worker
# Linux下启动Celery
# Celery -A 【项目名称】worker -l info
# Windows下启动Celery
# Celery -A 【项目名称】worker -l info -P eventlet
# 如果Windows下Celery不工作,输入如下命令
Celery -A 【项目名称】worker -l info --pool=solo
成功启动后的控制台提示如下:
(2)启动celery beat
celery -A 【项目名称】 beat -l info
成功启动后的控制台提示如下:
启动时遇到问题的部分解决方案:
- celery worker启动时报错:Celery ValueError: not enough values to unpack (expected 3, got 0):参考这篇文章:https://blog.csdn.net/showgea/article/details/109342664
- celery beat启动时报错:ERROR: Pidfile (celerybeat.pid) already exists.Seems we’re already running?···
删除项目根目录下的celery.pid文件即可
配置定时任务
登录django后台 admin 管理界面配置定时任务。
配置定时规则
进入Crontabs菜单增加一个定时规则。进入流程:首页->Celery Periodic Tasks->Crontabs->增加crontab
此菜单对应数据库表django_celery_beat_crontabschedule
:
上图字段分别对应数据库字段:minute(分钟)、hour(小时)、day_of_week(天/周)、day_of_month(天/月)、month_of_year(月/年)、timezone(时区)
其中表示时间的字段取值可以是:
- 字段表达数值的范围内:例如minute取值范围是[0,59]之间的整数;hour取值范围是[0,23]之间的整数;day_of_week取值范围是[0,6]之间的整数,对应周一到周六;day_of_month取值范围是[1,31]之间的整数,month_of_year取值范围是[1,12]之间的整数;
- 用
','
隔开的整数取值,表示单独的几个取值; - 用
'-'
连接的整数区间,表示一段取值范围; - 为
'*'
或者'*/ n'
【n表示在单位取值范围内的某个整数】,代表每隔n单位执行一次。
例如:
0,15,30 0 * * 3-5
表示每年3-5月的每天的0时0分、15分、30分各执行一次;* 3/* 0 8 8
表示8月8日每隔3小时后的某小时每分钟执行一次任务。
根据自己需求自己自定义添加一个定时规则即可。如果 Crontabs 不满足需求,可以在Clocked、Intervals、Solar Events这些菜单自行设定,这里不再赘述。
配置定时任务
进入 Periodic Tasks 菜单增加一个定时规则。进入流程:首页->Celery Periodic Tasks->Periodic Tasks->增加periodic tasks
此菜单对应数据库表django_celery_beat_periodictask
以下为示例:
填好相应信息保存后,任务就会按照定时规则自动运行了。
例如我此处设置了每分钟执行一次第4步中配置的task1
,位置参数为3、4,关键字参数type设置为multiply,在启动celery worker的控制台中即可看到打印结果:
如果要手动执行一次任务,还可以在Periodic Tasks的列表页执行如下操作:
虽然django_celery_beat_periodictask
表name 字段是唯一的,但是 task 可以重复写入,这也就意味着我们可以针对同一个 task 制定不同的定时策略。
查看任务运行结果
运行结果可以在admin后台管理界面看到,路径:首页->Celery Results->Task Results
参考链接:
- https://docs.celeryq.dev/en/main/index.html
- https://django-redis-chs.readthedocs.io/zh-cn/latest/index.html
- https://blog.csdn.net/qq_52385631/article/details/122783121
- https://blog.csdn.net/showgea/article/details/109362025
- https://blog.csdn.net/weixin_38054045/article/details/104111459
- https://blog.csdn.net/wuwei_201/article/details/129650089