celery实现异步任务或定时任务

1. Celery

在程序的运行过程中,我们经常会碰到一些耗时耗资源的操作,为了避免它们阻塞主程序的运行,我们经常会采用多线程或异步任务。比如,在 Web 开发中,对新用户的注册,我们通常会给他发一封激活邮件,而发邮件是个 IO 阻塞式任务,如果直接把它放到应用当中,就需要等邮件发出去之后才能进行下一步操作,此时用户只能等待再等待。更好的方式是在业务逻辑中触发一个发邮件的异步任务,而主程序可以继续往下运行。

Celery 是一个强大的分布式任务队列,它可以让任务的执行完全脱离主程序,甚至可以被分配到其他主机上运行。我们通常使用它来实现异步任务(async task)和定时任务(crontab)

2. 可以看到,Celery 主要包含以下几个模块:

  • 任务模块 Task

    包含异步任务和定时任务。其中,异步任务通常在业务逻辑中被触发并发往任务队列,而定时任务由 Celery Beat 进程周期性地将任务发往任务队列

  • 消息中间件 Broker

    Broker,即为任务调度队列,接收任务生产者发来的消息(即任务),将任务存入队列。Celery 本身不提供队列服务,官方推荐使用 RabbitMQ 和 Redis 等。

  • 任务执行单元 Worker

    Worker 是执行任务的处理单元,它实时监控消息队列,获取队列中调度的任务,并执行它

  • 任务结果存储 Backend

    Backend 用于存储任务的执行结果,以供查询。同消息中间件一样,存储也可使用 RabbitMQ, Redis 和 MongoDB 等。

3. 异步任务

(1)使用 Celery 实现异步任务主要包含三个步骤:

  1. 创建一个 Celery 实例
  2. 启动 Celery Worker
  3. 应用程序调用异步任务

(2)为了简单起见,对于 Broker 和 Backend,这里都使用 redis。在运行下面的例子之前,请确保 redis 已正确安装,并开启 redis 服务,当然,celery 也是要安装的。

pip install celery
pip install redis

 (3)异步任务目录结构

(4)init.py代码

from celery import Celery

app = Celery("celery_app")  # 创建celery实例
app.config_from_object("celery_app.celeryconfig")  # 通过celery实例加载配置模块

(5)celeryconfig.py代码

BROKER_URL = 'redis://127.0.0.1:6379/0'               # 指定 Broker
CELERY_RESULT_BACKEND = 'redis://127.0.0.1:6379/1'  # 指定 Backend

CELERY_TIMEZONE='Asia/Shanghai'                     # 指定时区,默认是 UTC
# CELERY_TIMEZONE='UTC'

CELERY_IMPORTS = (                                  # 指定导入的任务模块
    'celery_app.task1',
    'celery_app.task2'
)

(6)task1.py代码

# -*- coding: utf-8 -*-
import time
from celery_app import app


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

(7)task2.py代码

import time
from celery_app import app


@app.task
def xiangcheng(x, y):
    time.sleep(3)
    return x * y

(8)client.py代码

# -*- coding: utf-8 -*-

from celery_app.task1 import add
from celery_app.task2 import xiangcheng

add.apply_async(args=(8, 20), expires=10)  # 也可用add.delay(4, 10)
xiangcheng.apply_async(args=(8, 20), expires=10)  # 也可用xiangchneg.delay(4, 10)
print("哈哈哈")

(9)现在,让我们启动 Celery Worker 进程,在项目的根目录下执行下面命令:

celery -A celery_app worker --loglevel=info
  • 参数 -A 指定了 Celery 实例的位置,本例是在 celery_app 中,Celery 会自动在该文件中寻找 Celery 对象实例,当然,我们也可以自己指定
  • 参数 --loglevel 指定了日志级别,默认为 warning,也可以使用 -l info 来表示;

在生产环境中,我们通常会使用后台启动celery,详细参考网址:https://blog.csdn.net/weixin_42289273/article/details/108430922

(10)接着,运行 python client.py,它会发送两个异步任务到 Broker,在 Worker 的窗口我们可以看到如下输出:

[2021-02-19 11:39:55,945: INFO/MainProcess] Received task: celery_app.task1.add[09568f7e-ace4-468c-9b9a-801385b11c31]   expires:[2021-02-19
11:40:05.205059+08:00]
[2021-02-19 11:39:55,948: INFO/MainProcess] Received task: celery_app.task2.xiangcheng[84c1a9ae-dd0d-43d8-bb50-d7046756fd45]   expires:[2021
-02-19 11:40:05.945102+08:00]
[2021-02-19 11:39:58,976: INFO/SpawnPoolWorker-4] Task celery_app.task2.xiangcheng[84c1a9ae-dd0d-43d8-bb50-d7046756fd45] succeeded in 3.0270
000000000437s: 160
[2021-02-19 11:40:00,968: INFO/SpawnPoolWorker-1] Task celery_app.task1.add[09568f7e-ace4-468c-9b9a-801385b11c31] succeeded in 5.02300000000
1048s: 28

(11)delay 和 apply_async的区别

在前面的例子中,我们使用 delay() 或 apply_async() 方法来调用任务。事实上,delay 方法封装了 apply_async,如下:

def delay(self, *partial_args, **partial_kwargs):
    """Shortcut to :meth:`apply_async` using star arguments."""
    return self.apply_async(partial_args, partial_kwargs)
  • delay 是使用 apply_async 的快捷方式。apply_async 支持更多的参数,它的一般形式如下:
apply_async(args=(), kwargs={}, route_name=None, **options)

apply_async 常用的参数如下:

  • countdown:指定多少秒后执行任务
task1.apply_async(args=(2, 3), countdown=5)    # 5 秒后执行任务
  • eta (estimated time of arrival):指定任务被调度的具体时间,参数类型是 datetime
from datetime import datetime, timedelta

# 当前 UTC 时间再加 10 秒后执行任务
task1.multiply.apply_async(args=[3, 7], eta=datetime.utcnow() + timedelta(seconds=10))
  • expires:任务过期时间,参数类型可以是 int,也可以是 datetime
task1.multiply.apply_async(args=[3, 7], expires=10)    # 10 秒后过期

 4. 定时任务

(1)Celery 除了可以执行异步任务,也支持执行周期性任务(Periodic Tasks),或者说定时任务。Celery Beat 进程通过读取配置文件的内容,周期性地将定时任务发往任务队列。

(2)__init__.py、task1.py、task2.py三个文件代码不变,因为是定时任务不需要客户端触发,所以不需要client.py代码,只需要修改celeryconfig.py代码即可

(3)celeryconfig.py代码

from datetime import timedelta
from celery.schedules import crontab

# broker and banckend
BROKER_URL = 'redis://127.0.0.1:6379/0'               # 指定 Broker
CELERY_RESULT_BACKEND = 'redis://127.0.0.1:6379/1'  # 指定 Backend

# timezone
CELERY_TIMEZONE='Asia/Shanghai'                     # 指定时区,默认是 UTC
# CELERY_TIMEZONE='UTC'

# import
CELERY_IMPORTS = (                                  # 指定导入的任务模块
    'celery_app.task1',
    'celery_app.task2'
)

# schedules
CELERYBEAT_SCHEDULE = {
    'add-every-30-seconds': {
         'task': 'celery_app.task1.add',
         'schedule': timedelta(seconds=30),       # 每 30 秒执行一次
         'args': (6, 8)                           # 任务函数参数
    },
    'xiangcheng-at-some-time': {
        'task': 'celery_app.task2.xiangcheng',
        'schedule': crontab(hour=12, minute=33),   # 每天中午 12 点 33 分执行一次
        'args': (3, 7)                            # 任务函数参数
    }
}

(4) Celery Worker 进程

celery -A celery_app worker --loglevel=info

(5)Celery Beat 进程,定时将任务发送到 Broker

celery -A celery_app beat

这样任务1没30秒执行一次,任务2每天中午12点33分执行一次。

猜你喜欢

转载自blog.csdn.net/weixin_42289273/article/details/113860689
今日推荐