(二)Celery: 分布式异步消息任务队列


1. Celery 介绍

Celery 的分布式表明多个客户端可同时访问;它的异步性体现在:Celery 可以将生产完毕的任务放入消息队列,待消费端空闲后可自行去取,而不必阻塞生产端的运行。

Celery 本身不提供消息队列的功能,通常通过中间件(broker)实现,常用中间件有 RabbitMQ 和 Redis 等。同时为了便于查看任务执行过程中的状态,还需要存储器来保存每次任务的执行结果,可以使用各类数据库以及 Redis 等。


2. 基本使用

结合上一节的内容,首先定义一个 Celery 对象,中间件和存储(backend)均使用 Redis。

broker = 'redis://127.0.0.1:6379/1'
backend = 'redis://127.0.0.1:6379/2'
celery = Celery(main=__name__, broker=broker, backend=backend)

初始化 Celery 对象后,通常使用装饰器指定可被 Celery 调度的任务。

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

然后,通过命令行启动 Celery 服务。

celery worker -A tasks -l info

其中,-A 用于指定 Celery 实例、worker 启动 Celery 服务、-l 指定日志等级。

$ celery -A main worker -l info
 
 -------------- celery@${machinename} v5.2.7 (dawn-chorus)
--- ***** ----- 
-- ******* ---- Linux-5.15.0-41-generic-x86_64-with-debian-bullseye-sid 2022-07-31 10:38:07
- *** --- * --- 
- ** ---------- [config]
- ** ---------- .> app:         main:0x7f9ab8843f90
- ** ---------- .> transport:   redis://127.0.0.1:6379/1
- ** ---------- .> results:     redis://127.0.0.1:6379/2
- *** --- * --- .> concurrency: 16 (prefork)
-- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker)
--- ***** ----- 
 -------------- [queues]
                .> celery           exchange=celery(direct) key=celery
                

[tasks]
  . main.add

[2022-07-31 10:38:08,160: INFO/MainProcess] Connected to redis://127.0.0.1:6379/1
[2022-07-31 10:38:08,161: INFO/MainProcess] mingle: searching for neighbors
[2022-07-31 10:38:09,168: INFO/MainProcess] mingle: all alone
[2022-07-31 10:38:09,177: INFO/MainProcess] celery@cn0014010240l ready.

借由 Celery 提供的分布式功能,当有多个应用程序调用任务时,为了保证调用不出错,Celery 可指定队列名来确定应用程序操作指定任务。在上述启动 Celery 时,选项 [queues] 表明默认使用的队列是 celery,在启动时也可通过 -Q 命令指定监听的队列。

在输出的 [tasks] 选项可看到当前可调用任务为 main.add。然后在应用程序中通过 delay 或 apply_async 调用指定任务。

>>> from main import add
>>> r = add.delay(2, 6)
>>> r.ready()	# 判断当前任务是否执行完毕
True
>>> r.get()	# 获取任务执行结果
8		

其他的,可使用 r.state 或 r.status 获取任务状态。


3. 进阶使用

3.1 跨机通信

Celery 的一大特点是异步性,通过中间件,生产端和客户端可以实现独立运作。

首先在客户端发送任务到消息队列,其中包含任务中函数的名字以及对应的参数,后续生产端去拿函数来执行即可(这里的生产端指函数实现端、消费端指调用端)。为了使生产端和客户端跨机通信,我们放弃使用 from xx import xx 的方式导入任务函数。

要实现上述功能,首先在客户端一侧的 app_client.py 定义一个任务函数,其中不实现任何功能。

from celery import Celery
celery_app = Celery(main=__name__, 
                    backend='redis://127.0.0.1:6379/0', 
                    broker='redis://127.0.0.1:6379/1')
# 为了实现跨机通信,使用 name 字段将生产端和客户端指定为相同的名字
# name 的优先级高于 报名.文件名.add 的形式
@celery_app.task(name="csdn.blog.add")
def add(*args, **kwargs):
    pass

然后,在客户端 client.py 中往消息队列发送任务。

from app_client import add 

if __name__ == "__main__":
    ii, jj = [2, 3, 4, 5, 6, 7, 8, 9, 10], [7, 8, 9, 10, 11, 2, 3, 4, 5]
    t1res = []
    for i in range(9):
    	# 或使用 delay
        res = add.apply_async((ii[i], jj[i]))

上述代码通过循环往消息队列中发送 9 条数据,运行后打开 redis-cli 查看。

# 前面使用 6379:1 作为中间件,首先切换到 1
127.0.0.1:6379> select 1
OK
# 使用 keys 查看所有键值,可以看到还没被 Celery 取走的默认队列 celery 
127.0.0.1:6379[1]> keys *
1) "celery"
2) xxx
# 使用 type 查看键类型
127.0.0.1:6379[1]> type celery
list
# 其类型是列表,查看它的长度,和刚才我们发送的请求数一致
127.0.0.1:6379[1]> llen list
(integer) 9
# 使用 lindex celery 0 查看索引为 0 的内容,其他类似
127.0.0.1:6379[1]> lindex celery 0
xxx	# 内容太多了,这里不贴了,其中 argsrepr 项表明当前项参数为 (10, 5)

现在,消息队列里有 9 个刚才发送进去而待取出的任务函数,现在编写生产端(函数实现)app_server.py 代码。这里,将 app_client.py 里面的代码拷贝过来,然后实现函数即可。

from celery import Celery
celery_app = Celery(main=__name__, 
                    backend='redis://127.0.0.1:6379/0', 
                    broker='redis://127.0.0.1:6379/1')
# 为了实现跨机通信,使用 name 字段将生产端和客户端指定为相同的名字
@celery_app.task(name="csdn.blog.add")
def add(x, y):
    print(f'x: {x}, y: {y}\n')
    return x + y

然后通过命令运行 Celery,从打印内容可以看到在启动后,Celery 会自动去消息队列拿函数来执行。

celery worker -A app_server.celery_app -l info

现在去 redis-cli 查看键值,发现 celery 已经不存在,表明其中内容已全部执行完毕。

3.2 定时任务

接着上一节的内容,在跨机间实现任务定时功能,首先始终开启生产端的执行。

celery worker -A app_server.celery_app -l info

然后在 app_client.py 中加入定时属性。

from celery import Celery
from datetime import timedelta
from celery.schedules import crontab

celery_app = Celery(main=__name__, 
                    backend='redis://127.0.0.1:6379/0', 
                    broker='redis://127.0.0.1:6379/1')
celery_app.conf.update(
    # 如果设置某时某分执行任务,需要统一到北京时间
    timezone='Asia/Shanghai',   
    beat_schedule={
        # 任务列表,可设置多个任务
        "task1": {
            # 任务函数,和生产端的同名
            "task": "csdn.blog.add",
            # 时间属性,使用 timedelta 设置执行时间间隔、crontab 设置执行时间点
            # 每隔 10 秒执行一次
            "schedule": timedelta(seconds=10),
            # 每周一的上午 6:00 执行一次
            # "schedule": crontab(hours=6, minutes=0, day_of_week=1)
            # 任务函数的执行参数
            "args": (2, 3)
        },})

然后在生产端启动 Celery 的定时任务。

celery beat -A app_client.celery_app -l info

从打印结果可以看到,每隔 10 秒向消息队列写入任务函数及其参数,生成端也接收到了任务。

celery beat v4.4.0 (cliffs) is starting.
__    -    ... __   -        _
LocalTime -> 2022-08-20 10:19:01
Configuration ->
    . broker -> redis://127.0.0.1:6379/1
    . loader -> celery.loaders.app.AppLoader
    . scheduler -> celery.beat.PersistentScheduler
    . db -> celerybeat-schedule
    . logfile -> [stderr]@%INFO
    . maxinterval -> 5.00 minutes (300s)
[2022-08-20 10:19:01,157: INFO/MainProcess] beat: Starting...
[2022-08-20 10:19:01,451: INFO/MainProcess] Scheduler: Sending due task task1 (csdn.blog.add)
[2022-08-20 10:19:11,428: INFO/MainProcess] Scheduler: Sending due task task1 (csdn.blog.add)
[2022-08-20 10:19:21,429: INFO/MainProcess] Scheduler: Sending due task task1 (csdn.blog.add)

其他的可以设置更复杂的定时任务,如在 beat_schedule 选项中加入更多任务、在任务中加入更多参数等。


4. 总结

本文介绍了 Celery 的安装和基本使用方法,官方文档:https://docs.celeryq.dev/en/stable/index.html


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值