Django 之 Celery

什么是任务队列

  • 任务队列一般用于线程或计算机之间分配工作的一种机制。
  • 任务队列的输入是一个称为任务的工作单元,有专门的职程(Worker)进行不断的监视任务队列,进行执行新的任务工作。 Celery通过消息机制进行通信,通常使用中间人(Broker)作为客户端和职程(Worker)调节。启动一个任务,客户端向消息队列发送一条消息,然后中间人(Broker)将消息传递给一个职程(Worker),最后由职程(Worker)进行执行中间人Broker)分配的任务。
  • Celery 可以有多个职程(Worker)和中间人(Broker),用来提高Celery的高可用性以及横向扩展能力。
  • Celery 是用 Python 编写的,但协议可以用任何语言实现。除了 Python
    语言实现之外,还有Node.js的node-celery和php的celery-php。
  • 可以通过暴露 HTTP 的方式进行,任务交互以及其它语言的集成开发。

盗一张图~~~~:
在这里插入图片描述
图解:

  • 1.任务发布者:web 应用产生的事件,如:发送邮件、短信等
  • 2.任务调度器:定时任务
  • 3.Broker:任务队列,支持 redis 或 RabbitMQ
  • 4.Worker:获取任务并处理,任务处理者
  • 5.Datebase:存储结果

总结:task 和 Worker 是通过一个消息队列(redis 或者 RabbitMQ)来解决任务和任务处理者的强耦合问题,任务和任务处理者彼此之间不直接通讯而通过消息队列来进行通讯,所以生产完任务之后不用等任务处理者处理,直接扔给消息队列,任务处理者不找任务要数据,而是直接从消息队列里取,消息队列就相当于一个缓冲区,平衡了任务和任务处理者的处理能力,通过该模式的工作能力来提高整体处理数据的速度。

安装 Celery

# 安装必选项
celery==5.0.5
django==3.2
redis==3.5.3

# 可选,windows下运行celery 4以后版本,还需额外安装eventlet库
 eventlet

之前需要优化的案例

def inset(request):
    time.sleep(20)

    return HttpResponse('ok')

打比方我在上述视图函数中等待20秒,这个时候用户访问进来就会一直等待,这样肯定是不行的,这里只是举例,如果真有程序耗时20秒钟呢,这就很致命了,这就用到了 Celery,将这些耗时的任务放到 Celery 中去处理,达到一个并行的处理。

Celery 配置

项目文件夹下新建一个 celery 文件:

 - manytabletest/
   - manage.py
   - project/
     - __init__.py # 修改这个文件
     - celery.py   # 新增这个文件
     - asgi.py
     - settings.py
     - urls.py
     - wsgi.py

celery 文件内容:

import os

from datetime import timedelta
from celery import Celery
from django.conf import settings

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'manytabletest.settings')

app = Celery('manytabletest')
# namespace='CELERY'作用是允许你在Django配置文件中对Celery进行配置
# 但所有Celery配置项必须以CELERY开头,防止冲突
app.config_from_object('django.conf:settings', namespace='CELERY')
# 自动从django注册中发现任务
app.autodiscover_tasks(settings.INSTALLED_APPS)
# 使用 django orm 来保存结果数据
app.loader.override_backends['django-db'] = 'django_celery_results.backends.database:DatabaseBackend'

# 一个测试任务
@app.task(bind=True)
def debug_task(self):
     print(f'Request: {self.request!r}')
# 当bind=True时,add函数第一个参数是self,指的是task实例
@task(bind=True)  # 第一个参数是self,使用self.request访问相关的属性

init.py 文件内容:这里也可以不需要配置,只是为了导入时的一些优化

from .celery import app as celery_app

__all__ = ('celery_app')

settings.py中:

# 最重要的配置,设置消息broker,格式为:db://user:password@host:port/dbname
# 如果redis安装在本机,使用localhost
# 如果docker部署的redis,使用redis://redis:6379
CELERY_BROKER_URL = "redis://127.0.0.1:6379/0"

# celery时区设置,建议与Django settings中TIME_ZONE同样时区,防止时差
# Django设置时区需同时设置USE_TZ=True和TIME_ZONE = 'Asia/Shanghai'
CELERY_TIMEZONE = TIME_ZONE


# 为django_celery_results存储Celery任务执行结果设置后台
# 格式为:db+scheme://user:password@host:port/dbname
# 支持数据库django-db和缓存django-cache存储任务状态及结果
CELERY_RESULT_BACKEND = "redis://127.0.0.1:6379/1"
# CELERY_RESULT_BACKEND = "django-db" 需要安装 django-celery-results==2.0.1 并且需要同步数据库和添加到 INSTALLED_APPS 中
# celery内容等消息的格式设置,默认json
CELERY_ACCEPT_CONTENT = ['application/json', ]
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'

# 为任务设置超时时间,单位秒。超时即中止,执行下个任务。
CELERY_TASK_TIME_LIMIT = 5

# 为存储结果设置过期日期,默认1天过期。如果beat开启,Celery每天会自动清除。
# 设为0,存储结果永不过期
# CELERY_RESULT_EXPIRES = xx

# 任务限流
CELERY_TASK_ANNOTATIONS = {
    
    'tasks.test': {
    
    'rate_limit': '10/s'}}

# Worker并发数量,一般默认CPU核数,可以不设置
CELERY_WORKER_CONCURRENCY = 2

# 每个worker执行了多少任务就会死掉,默认是无限的
CELERY_WORKER_MAX_TASKS_PER_CHILD = 200

完整配置见官网:https://docs.celeryproject.org/en/stable/userguide/configuration.html

启动之前要注意:redis 是否启动 celery 是否安装配置成功

启动 Celery

启动 worker(异步任务):

celery -A manytabletest worker -l info
windows 10+ 本地运行需要加 ‘–pool=solo’

启动 scheduler(定时任务):

celery -A manytabletest beat -l info

如果能看到 ready 就是 Celery 已经启动成功并准备开始工作了。
在这里插入图片描述

编写任务

一般是在 app 下新建一个 task 文件,里面写周期性的任务,使用 @app.task 装饰器定义。
task.py:

import time

from manytabletest.celery import app

@app.task
def test(x, y):
    time.sleep(10)  # 为了模拟异步任务这里等待十秒
    return x+y
    
# task方法参数
name:可以显式指定任务的名字;默认是模块的命名空间中本函数的名字。
serializer:指定本任务的序列化的方法;
bind:一个bool值,设置是否绑定一个task的实例,如果绑定,task实例会作为参数传递到任务方法中,可以访问task实例的所有的属性,即前面反序列化中那些属性
base:定义任务的基类,可以以此来定义回调函数,默认是Task类,我们也可以定义自己的Task类
default_retry_delay:设置该任务重试的延迟时间,当任务执行失败后,会自动重试,单位是秒,默认3分钟;
autoretry_for:设置在特定异常时重试任务,默认False即不重试;
retry_backoff:默认False,设置重试时的延迟时间间隔策略;
retry_backoff_max:设置最大延迟重试时间,默认10分钟,如果失败则不再重试;
retry_jitter:默认True,即引入抖动,避免重试任务集中执行;

异步调用任务

Celery调用异步任务有三个方法:

  1. app.send_task 不是很常用

    # tasks.py
    from celery import Celery
    app = Celery()
    def add(x,y):
        return x+y
    
    app.send_task('tasks.add',args=[10,20]) 
     # 参数基本和apply_async函数一样# 但是send_task在发送的时候是不会检查tasks.add函数是否存在的,即使为空也会发送成功,所以celery执行是可能找不到该函数报错;
    
  2. Task.delay

    def inset(request):
        test.delay(args=[3,5])
        return HttpResponse('ok')
     Task.delay(参数1, 参数1, 键值, kwargs2=value_2)
    
  3. Task.apply_async 与delay类似,但支持更多参数

     def inset(request):
        result = test.apply_async(args=[4,5])
        return HttpResponse('ok')
     task.apply_async(args=[arg1, arg2], kwargs={
          
          key:value, key:value})
    

测试:当你通过浏览器访问链接时,你根本感受不到2s的延迟,页面可以秒开,同时你会发现终端的输出如下所示,显示任务执行成功。
在这里插入图片描述
使用 apply_async 方法调用 test 任务还能打印任务 id 和任务 status

def inset(request):
    result = test.apply_async(args=[3,5])
    print(result.id)
    print(result.status)
    return HttpResponse('ok')

在这里插入图片描述
额外参数:

countdown : 等待一段时间再执行.
add.apply_async((2,3), countdown=5)

eta : 定义任务的开始时间.这里的时间是UTC时间,这里有坑
add.apply_async((2,3), eta=now+tiedelta(second=10))

expires : 设置超时时间.
add.apply_async((2,3), expires=60)

retry : 定时如果任务失败后, 是否重试.
add.apply_async((2,3), retry=False)

retry_policy : 重试策略.
  max_retries : 最大重试次数, 默认为 3.
  interval_start : 重试等待的时间间隔秒数, 默认为 0 , 表示直接重试不等待.
  interval_step : 每次重试让重试间隔增加的秒数, 可以是数字或浮点数, 默认为 0.2
  interval_max : 重试间隔最大的秒数, 即 通过 interval_step 增大到多少秒之后, 就不在增加了, 可以是数字或者浮点数, 默认为 0.2 

Celery 存储结果

  • 方式一:使用 django-celery-results

    	 通过pip安装django-celery-results
    	 # 支持数据库django-db和缓存django-cache存储任务状态及结果
    	 CELERY_RESULT_BACKEND = "django-db" # 需要将其加入到INSTALLED_APPS并使用migrate命令迁移创建数据表
    	 # celery内容等消息的格式设置,默认json
    	 CELERY_ACCEPT_CONTENT = ['application/json', ]
    	 CELERY_TASK_SERIALIZER = 'json'
    	 CELERY_RESULT_SERIALIZER = 'json'
    
  • 方式二:使用 redis ,上面配置同样适用

    CELERY_RESULT_BACKEND = "redis://127.0.0.1:6379/1" # redis 可以作为消息队列 同样也可以保存结果数据
    

设置定时任务

  • 方式一:django-celery-beat

    	# 使用 django-celery-beat 需要 pip 安装 并加入到 INSTALLED_APPS
    	# django-celery-beat提供了两种添加定时或周期性任务的方式,一是直接在settings.py中添加,二是通过Django admin后台添加。
    	 from datetime import timedelta
     	CELERY_BEAT_SCHEDULE = {
          
          
         "test": {
          
          
             "task": "app.tasks.test",
             'schedule': timedelta(seconds=5),  # 5秒钟执行一次
             'args': (7, 20) # 传递参数
         }
    
  • 方式二:使用 Celery 提供的 beat_schedule

    app.conf.beat_schedule = {
          
          
        'test': {
          
          
            'task':'manytable.tasks.test',
            'schedule': timedelta(seconds=5),
            'args': (7, 20),
        }
    }
    

使用 Crontab 设置定时任务

如果你希望在特定的时间(某月某周或某天)执行一个任务,你可以通过crontab设置定时任务:

app.conf.beat_schedule = {
    
    
    'test': {
    
    
        'task':'manytable.tasks.test',
        'schedule': crontab(hour=7, minute=30, day_of_week=1),  # day_of_week:周几  hour:小时(点)  minute:分钟
        'args': (7, 20),
    }
}

启动定时任务

 # 开启任务调度器
 Celery -A manytabletest beat
 
 # Linux下开启Celery worker
 Celery -A manytabletest worker -l info
 
 # windows下开启Celery worker
 Celery -A manytabletest worker -l info -P eventlet
 
 # windows下如果报Pid错误
 Celery -A manytabletest worker -l info --pool=solo

报错

ModuleNotFoundError: No module named 'django-db’
解决办法:

  1. 安装 django-celery-results 并加入到 加入到INSTALLED_APPS 并使用 migrate 命令迁移创建数据表
  2. CELERY_RESULT_BACKEND = “django-db”
  3. celery.py中加入如下代码:
    app.loader.override_backends[‘django-db’] = ‘django_celery_results.backends.database:DatabaseBackend’

windos 上面使用 celery 会有很多错误,如解释器版本不匹配、安装包不兼容等,如遇到问题再具体解决

参考文献

Celery 中文手册:https://www.celerycn.io/
Celery 参数配置:https://docs.celeryproject.org/en/stable/userguide/configuration.html

猜你喜欢

转载自blog.csdn.net/qq_39253370/article/details/116377577
今日推荐