任务队列:celery快速入门及django中celery的用法

一、celey的简介

Celery是一个专门用于实时处理的任务队列,同时也支持任务调度。它是由python语言编写的,但也有其他语言的实现,如用于Node.js的node-celerynode-celery-ts ,以及用于php的celery-php

任务队列是一种机制,一般用于线程或计算机之间分配工作(任务)。

1.1 celery的工作机制

要理解celery的工作机制,我们需要先了解一下几个celery的概念:

  • worker:

    专门对任务队列进行不间断的监视,以便执行新的任务。

  • broker:

    用于在客户端(生成任务的程序)和worker中间进行交互。当客户端需要启动一个任务时,就需要向broker中添加一个消息,再由broker将消息传递给worker去执行。

  • result stores:

    用于存储任务的执行结果。

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

Celery 需要消息中间件来充当broker, RabbitMQ 和 Redis 就是两个不错的选择。而result stores可以使用redis、memcached等。

1.2 安装celery(5.2版本)

Celery 是一个资金很少的项目,因此我们不支持 Microsoft Windows。请不要提出与该平台相关的任何问题。

使用pip安装:

pip install -U Celery

Celery 自定义了一组用于安装 Celery 和特定功能的依赖。我们可以在中括号加入需要依赖,以简化安装:

pip install "celery[librabbitmq]"
pip install "celery[librabbitmq,redis,auth,msgpack]"

更多依赖参考官方文档:传送门

二、celery快速入门

2.1 选择broker

Celery 通过消息机制进行通信, 所以需要一个中间件来进行接收和发送消息,通常以独立的服务形式出现,称为Broker。

  • RabbitMQ:

    RabbitMQ 的功能比较齐全、稳定、便于安装。在生产环境来说是首选的。

  • Redis(新手建议使用redis):

    Redis 功能比较全,但是如果突然停止运行或断电会造成数据丢失。

我们这里就以redis为例,请提前安装好redis数据库和redis模块。

除以上提到的Broker之外,还支持:

NameStatusMonitoringRemote Control
RabbitMQStableYesYes
RedisStableYesYes
Amazon SQSStableNoNo
ZookeeperExperimentalNoNo

2.2 celery的简单使用

2.2.1 创建app

第一步是创建celery的实例对象,称其为app,app中包含 Celery 中执行操作的所有入口点,例如创建任务、Worker等,并且app必须能被其他模块导入

这里为了简练,我们就只在一个模块中进行演示。

创建一个tasks.py文件:

from celery import Celery

app = Celery('tasks', broker='redis://ip:端口/数据库编号')

@app.task
def add(x, y):
    return x + y
  • Celery的第一个参数tasks是当前模块的名称,当在__main__模块中定义任务时,可以自动生成名称。

  • 第二个参数broker是broker的链接url,默认用的是Rabbit MQ:pyamqp://guest@localhost//。这里使用了redis。

  • add方法便是我们的任务了。

2.2.2 运行celery worker 服务

现在需要通过终端运行worker服务,以此执行任务:

celery -A tasks worker --loglevel=INFO

2.2.3 调用任务

新建一个终端,在python命令行中执行:

>>> from tasks import add
>>> add.delay(4, 4)  # 将add提交给celery去执行

在运行worker服务的终端可以看到任务的执行情况,比如成功的情况:Task tasks.add[dbaf2348-f7e4-4869-8b1c-61f71a58291e] succeeded in 0.00015922699822112918s: 8

调用任务会返回一个AsyncResult实例,这可以用于检查任务的状态,等待任务完成,或获取其返回值(如果任务失败,则获取异常和回溯)。这个功能默认是不开启的,如果开启则需要配置 Celery 的结果后端,下一小节会详细说明。

2.2.4 保存结果

如果需要跟踪任务的状态,则需要在某处存储任务的状态信息。Celery 内置了一些result stores:redis、memcached、django orm等等。

我们这里继续使用redis,还是在创建app时设置:

app = Celery('tasks', backend='redis://localhost', broker='redis://localhost')

更多result stores后端配置,参考官方文档:传送门

现在已经配置结果后端,重新调用执行任务。会得到调用任务后返回的一个 AsyncResult 实例:

>>> result = add.delay(4, 4)

打印result可以看到任务的UUID,比如:3b551c49-9411-408c-9b28-5da1080aa2e0

而在redis中可以看到结果如下:

{
  "status": "SUCCESS",
  "result": 8,
  "traceback": null,
  "children": [],
  "date_done": "2022-01-11T10:49:39.603272",
  "task_id": "774a5182-ea96-43c6-80b7-33d7bb2bf873"
}

ready() 方法返回任务是否已完成处理:

>>> result.ready()
False

我们也可以使用get()方法,等待任务完成并返回结果,但这就成了同步调用,所以很少用:

>>> result.get(timeout=1)  # 设置最大等待时间为1秒
8

如果任务抛出了一个异常,get() 会再次引发异常,可以通过 propagate参数阻止这一行为:

>>> result.get(propagate=False)

如果任务引发了异常,我们也可以访问原始的回溯信息:

>>> result.traceback

2.3 配置

我们之前的配置,是通过传参的方式完成的,celery也支持通过一个配置模块来进行配置,并且在大型项目中,推荐使用模块配置。

  • 加载配置模块:

    app.config_from_object('celeryconfig')
    

    配置模块默认的名称为’celeryconfig’,我们也可以起别的名字。

  • celeryconfig.py的内容(部分):

    broker_url = 'pyamqp://'
    result_backend = 'rpc://'
    
    task_serializer = 'json'
    result_serializer = 'json'
    accept_content = ['json']
    timezone = 'Europe/Oslo'
    enable_utc = True
    

    完整的配置参考官方文档:传送门.

  • 检查配置是否正确的命令:

    python -m celeryconfig
    

三、celery进阶说明

先建立一个celery项目,项目结构:

proj/__init__.py
    /celery.py
    /tasks.py

proj/celery.py:

from celery import Celery

app = Celery('proj',
             broker='redis://127.0.0.1:6379/1',
             backend='redis://127.0.0.1:6379/2',
             include=['proj.tasks']) 


# 可选配置
app.conf.update(
    result_expires=3600,
)

if __name__ == '__main__':
    app.start()

在此程序中,创建了 Celery 实例(也称 app),如果需要使用 Celery,导入即可。

include参数:当worker启动时要导入的模块列表。

proj/tasks.py:

from .celery import app


@app.task
def add(x, y):
    return x + y


@app.task
def mul(x, y):
    return x * y


@app.task
def xsum(numbers):
    return sum(numbers)

3.2 运行worker

在终端中执行:

celery -A proj worker -l INFO

成功运行后,可以看到一部分日志消息:

 -------------- celery@hugh-vm v5.2.3 (dawn-chorus)
--- ***** ----- 
-- ******* ---- Linux-5.13.0-23-generic-x86_64-with-glibc2.34 2022-01-11 20:55:09
- *** --- * --- 
- ** ---------- [config]
- ** ---------- .> app:         proj:0x7fa178fb8070
- ** ---------- .> transport:   redis://127.0.0.1:6379/1
- ** ---------- .> results:     redis://127.0.0.1:6379/2
- *** --- * --- .> concurrency: 4 (prefork)
-- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker)
--- ***** ----- 
 -------------- [queues]
                .> celery           exchange=celery(direct) key=celery
                

[tasks]
  . proj.tasks.add
  . proj.tasks.mul
  . proj.tasks.xsum

[2022-01-10 20:55:09,699: INFO/MainProcess] Connected to redis://127.0.0.1:6379/1

transport:broker的连接url;

result:result store的连接url;

concurrency: 同时处理任务的工作进程数量,所有的进程都被占满时,新的任务需要进行等待其中的一个进程完成任务才能执行进行任务

默认的并发数为当前计算机的 CPU 数,可以通过设置 celery worker-c 项进行自定义设置并发数。

3.3 停止worker

使用 Control + c 就可以停止职程(Worker)。

3.4 后台运行

在生产环境中,如果需要后台运行职程(Worker),可以参阅 守护进程:Daemonization

可以使用 celery multi 命令在后台启动一个或多个职程(Worker):

$ celery multi start w1 -A proj -l info
celery multi v4.0.0 (latentcall)
> Starting nodes...
    > w1.halcyon.local: OK

也可以进行重启:

$ celery  multi restart w1 -A proj -l INFO
celery multi v4.0.0 (latentcall)
> Stopping nodes...
    > w1.halcyon.local: TERM -> 64024
> Waiting for 1 node.....
    > w1.halcyon.local: OK
> Restarting node w1.halcyon.local: OK
celery multi v4.0.0 (latentcall)
> Stopping nodes...
    > w1.halcyon.local: TERM -> 64052

或者是停止:

$ celery multi stop w1 -A proj -l INFO

stop 命令是异步的,所以不会等待Worker关闭。可以通过 stopwait 命令进行停止运行,可以保证在退出之前完成当前正在执行的任务:

$ celery multi stopwait w1 -A proj -l info

3.5 --app(简写为-A)的参数

——app参数以module.path:attribute的形式,指定要使用的celery应用实例。

但它也支持一种简便方式。如果只指定了一个包名,它将尝试搜索应用实例,顺序如下:

--app=proj为例:

  • 名为 proj.app 的属性
  • 名为 proj.celery 的属性
  • 模块 proj 中值为 Celery 应用程序的任何属性

如果还没有找到,将尝试检索名为 proj.celery的子模块

  • 名为 proj.celery.app 的属性
  • 名为 proj.celery.celery 的属性
  • 模块 proj.celery 中值为 Celery 应用程序的任何属性

在此方案模仿文档中使用的实例,即针对单个模块包含的proj:app ,以及大型项目的 proj.celery:app

3.6 程序调用

使用 delay() 方法进行调用,它实际上是 apply_async() 的快捷使用:

add.delay(2, 2)  # add是任务的名称
add.apply_async((2, 2))  # 与上面的代码等价

apply_async() 可以指定调用时执行的参数,例如使用的任务队列、延迟执行的时间等:

add.apply_async((2, 2), queue='lopri', countdown=10)

上面的实例中,任务被下发到 lopri 队列中,任务下发之后最快会在10秒之后执行。

每一个任务被调用时会赋值一个的任务ID(UUIID)

delay()apply_async() 方法会返回一个 AsyncResult 实例,可以用于进行跟踪任务状况。如果进行跟踪任务状态,需要设置一个结果后端,以便于存储。

3.7 执行延迟任务

from datatime import datetime, timedelta

# 从当前时间开始往后延迟1天,得到的一个新的时间
tomorrow = datetime.utcnow() + timedelta(days=1)
add.apply_async((2,2),eta=tomorrow)  # eta必须传入python时间对象

当然,也可以使用之前提到过的countdown参数,可以向它传入延迟的秒数。

3.8 执行定时任务

# 禁用utc,使用北京时间
app.conf.timezone = 'Asia/Shanghai'
app.conf.enable_utc = False

app.conf.beat_schedule = {
    'add-every-30-seconds': {    # 定时配置的名称
        'task': 'tasks.add',     # 任务
        'schedule': 30.0,        # 设置执行间隔,30秒
        'args': (16, 16)
    },
}

四、django中celery的用法

celery 5.0.x支持Django 1.11 及以上版本。对于Django 1.11 之前的版本,请使用celery 4.4.x。

先看一下项目布局:

- django_proj/
  - manage.py
  - proj/
    - __init__.py
    - settings.py
    - urls.py

4.1 创建app

依旧是创建一个celery实例,在celery.py中:

import os

from celery import Celery

# 为'celery'程序设置默认的Django设置模块。导就对了!
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'proj.settings')

app = Celery('proj')

# 在这里使用字符串意味着worker不必将配置对象序列化为子进程
# namespace='CELERY'意味着所有与celery相关的配置key都应该有一个'CELERY_'前缀。
app.config_from_object('django.conf:settings', namespace='CELERY')

# 从所有注册的Django app中加载任务模块。
app.autodiscover_tasks()


@app.task(bind=True)  
def debug_task(self):
print(f'Request: {self.request!r}')

接下来,回顾一下上面的代码。

app.config_from_object('django.conf:settings', namespace='CELERY')

这一句代表使用django的设置模块作为celery的设置模块,并且与celery相关的配置项都需要使用“CELERY_”作为前缀。当然这是可选的,我们可以另外建立一个celery的配置模块,而不是和django混在一起。

app.autodiscover_tasks()

之前我们是在单独的tasks.py模块中定义所有的任务,而celery则提供了一种自动发现任务的方法。使用上面的方法,celery就会去注册过的django app中,寻找tasks.py文件。

最后,装饰器中的bind=True表示可以通过self关键字,访问任务实例的属性和方法。

4.2 确保app被正确加载

然后需要在django_proj/proj/__init__.py模块中导入这个celery实例app。确保app在Django启动时被加载,这样@shared_task装饰器(后面会提到)就会使用它。

# 这将确保当Django启动时app总是被导入的,这样@shared_task就会使用这个app
from .celery import app as celery_app

__all__ = ('celery_app',)

4.3 使用@shard_task装饰器

有些情况下,我们编写的任务是可重用的,这意味着我们不确定会不会通过celery实例对象对任务进行处理。而默认的@app.task()装饰器则绑定了一个celery实例,不符合我们的要求。于是,就有了shard_task装饰器,它可以让我们在没有任何具体celery实例的情况下创建任务。

from celery import shared_task


@shared_task
def add(x, y):
    return x + y
  • 3
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

花_城

你的鼓励就是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值