celery 简介

一. 介绍

  1. celery:翻译过来叫---> "芹菜", 它是一个 分布式的异步任务 框架

  2. 官方文档

  3. celery是独立的服务
    ( 1 ) 可以不依赖任何服务器,通过自身命令,启动服务
    ( 2 ) celery服务为其他项目服务提供异步解决任务需求的

注:会有两个服务同时运行,一个是项目服务,一个是celery服务,项目服务将需要异步处理的任务交给celery服务,celery就会在需要时异步完成项目的需求

  1. celery 的作用
    ( 1 ) 完成异步任务:可以提高项目的并发量,之前开启线程做,现在使用celery做
    ( 2 ) 完成延迟任务
    ( 3 ) 完成定时任务
  2. 架构
    ( 1 ) 消息中间件:broker 提交的任务(函数)都放在这里,celery本身不提供消息中间件,需要借助于第三方:redis,rabbitmq
    ( 2 ) 任务执行单元:worker,真正执行任务的地方,一个个进程,执行函数。
    ( 3 ) 结果存储:backend,函数return的结果存储在这里,celery本身不提供结果存储,借助于第三方:redis,数据库,rabbitmq。

二. 使用(简单小案例)

celery 路径导入是个大坑,切记切记。

1. 安装模块

# 安装模块
pip install celery

# 安装redis (需要用到)
pip install redis

# 安装 eventlet (windows 系统需要用到,因为 celery 不支持 windows)
pip install eventlet

2. 代码

(1). celery_task.py

"""------------------------初始化工厂-------------------------"""

# 导入
from celery import Celery
import time

# 存储结果的地方 (可以不用 redis)
broker = 'redis://127.0.0.1:6379/1'
# 提交任务的地方 (可以不用 redis)
backend = 'redis://127.0.0.1:6379/2'

app = Celery('app', broker=broker, backend=backend)

# 加上装饰器, 才算是celery的任务。
@app.task
def add(a, b): # 写任务,任务就是函数。

    time.sleep(2)
    print(a + b)
    return a + b


"""------------------------启动工厂-------------------------"""
#"""需要进入 celery_task.py 所在的文件下执行"""
# 启动后等待任务提交,如果有任务提交,会自动执行。

# windos 系统运行:《 celery_task,就是文件名 》
celery -A celery_task worker -l info -P eventlet

# 非 windos 运行:mac linux
celery -A celery_task  worker -l info

(2). submit_task.py, 提交任务

《1》. add.delay(3, 4) 函数名加括已经调用了,非项目中,右键执行即可。

《2》. 在项目中,把函数放在需要执行的位置进行调用,项目执行时,就会被调用。

《3》. 在项目中,用异步任务。

from celery_task import add

# 同步任务,直接就执行了。
res = add(3, 4)
print(res) # 打印结果:7


# 异步任务了,不会将代码结果执行出来, 而是放在了 borker 层中, 等待 worker 层取任务执行。
# 结果是任务 id 号,celery 会根据 id 号执行任务,最后返回结果。
res = add.delay(3, 4)
print(res) # 打印结果:8b392207-f69d-4544-8d96-953bb8453be0 (任务 id 号)

(3). check_task.py, 查看任务执行结果

# 导入任务
from celery_task import app

from celery.result import AsyncResult

# (2)中提交后得到的任务 id
id = "12313"

if __name__ == '__main__':
    res = AsyncResult(id=id, app=app)
    if res.successful():
        result = res.get()
        print(result)
    elif res.failed():
        print("任务失败")
    elif res.status == 'PENDING':
        print("任务等待中执行")
    elif res.status == 'RETRY':
        print("任务异常后正在重试")
    elif res.status == 'STARTED':
        print("任务已经开始执行")

三. 包结构(项目级)

celery 路径导入是个大坑,切记切记。

1. 目录结构

# 具体可见下面图片

项目:
        # 新建包名
    |—— celery_task
          |—— __init__.py
              # 初始化 app
          |—— init_celery.py
              # 任务1
          |—— add_task
              |—— __init__
              |—— add_task.py  # 编写任务的文件
              # 任务2
          |—— send_task
              |—— __init__
              |—— send_task.py # 编写任务的文件


"""下面提交任务和获取结果,在项目中,就需要在业务代码中进行了。"""

    # 提交任务1
|—— summit_task_01.py
    # 提交任务2
|—— summit_task_02.py
    # 获取任务执行的结果
|—— get_result.py

2. 代码

启动命令

image

# 启动命令
celery -A celery_task.init_celery worker -l info -P eventlet

# 如果(1)中的文件名是 celery.py 命令可以如下:
celery -A celery_task worker -l info -P eventlet

(1). init_celery.py (初始化)

写完初始化代码,就可以启动 celery。启动命令如上面图片所示:
图片灰色背景部分就是初始化文件的路径

from celery import Celery

broker = 'redis://127.0.0.1:6379/1'
# 提交任务的地方 (可以不用 redis)
backend = 'redis://127.0.0.1:6379/2'

# 1. 因为任务代码放到了其他文件中,需要 include 指定位置。
# 路径为:任务函数所在的文件名为止
app = Celery('app', broker=broker, backend=backend, include=['celery_task.add_task.add_task', 'celery_task.send_task.send_task'])

(2). add_task.py(任务)

from celery_task.init_celery import app

@app.task()
def add(a, b):
    return a + b

(3). summit_add_task.py (提交)

from celery_task.add_task.add_task import add

# 脚本形式运行 《项目里在业务代码中》
if __name__ == '__main__':
    res = add.delay(1, 1)
    print(res)

(4). get_result.py (取结果)

from celery_task import app

from celery.result import AsyncResult

#(3)中提交后得到的任务 id
id = "12313"

# 脚本形式运行 《项目里在业务代码中》
if __name__ == '__main__':
    res = AsyncResult(id=id, app=app)
    if res.successful():
        result = res.get()
        print(result)
    elif res.failed():
        print("任务失败")
    elif res.status == 'PENDING':
        print("任务等待中执行")
    elif res.status == 'RETRY':
        print("任务异常后正在重试")
    elif res.status == 'STARTED':
        print("任务已经开始执行")

四:官方推荐(结构)

1. 项目结构

    |—— 大项目名
        |—— 小项目名
            |—— __init__.py
            |—— settings
                |—— __init__.py
                |—— dev.py
            |—— celery01.py
        |—— app01
            ...
            |—— task.py
            |—— views.py

1. 创建 celery

在《小项目》下创建 celery 文件, 本项目以 celery01.py 为例

官方推荐 文件名为 celery.py, 但是有导包问题。

celery.py 文件中的代码如下:

import os
import django
from celery import Celery

# 导入配置文件
os.environ.setdefault('DJANGO_SETTINGS_MODULE', '<配置文件路径> eg:luffy_api.settings.dev')

# 实例化 celery 对象
app = Celery('celery_demo')

方案一:代码在下方


# 方案二:
# 自动检索配置文件中获取相关 celery 配置
app.config_from_object('django.conf:settings')

# 自动检索 app 下的task.py文件
app.autodiscover_tasks()
# app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)

方案一代码:

app.conf.update(

    BROKER_URL='redis://127.0.0.1:6379/1',

    # BACKEND配置,使用redis
    RESULT_BACKEND='redis://127.0.0.1:6379/2',

    ACCEPT_CONTENT=['json'],

    TASK_SERIALIZER='json',

    # 结果序列化方案
    RESULT_SERIALIZER='json',

    # 任务结果过期时间,秒
    TASK_RESULT_EXPIRES=60 * 60 * 24,

    # 时区配置
    TIMEZONE='Asia/Shanghai',
)

方案二代码:

可以放在任意配置文件中,被调用了就行。

BROKER_URL='redis://127.0.0.1:6379/1',

# BACKEND配置,使用redis
RESULT_BACKEND='redis://127.0.0.1:6379/2',

ACCEPT_CONTENT=['json'],

TASK_SERIALIZER='json',

# 结果序列化方案
RESULT_SERIALIZER='json',

# 任务结果过期时间,秒
TASK_RESULT_EXPIRES=60 * 60 * 24,

# 时区配置
TIMEZONE='Asia/Shanghai',

2. 引入 celery-app

在小项目下的 __init__.py 文件内容如下:

from .celery01 import app as celery_app

__all__ = ('celery_app', )

3. 创建 task

在每一个 app 下创建 task.py 文件(命名必须是 task/tasks), 写 celery 任务

from celery import shared_task

# 使用装饰器装饰后, 就是 celery 任务函数了。
@shared_task()
def add1(a, b):
    return a + b

4. 调用任务

在需要使用的地方调用,比如 views.py

# 引入 task.py 中的任务函数
from task import add1

# 使用
add1.delay(a, b)

5. 启动任务

# celery -A <celery的路径名> worker -l debug -P eventlet
celery -A luffy_api.celery01 worker -l debug -P eventlet

五,异步,延迟,定时任务

1. 异步任务

# 导入任务
from celery_task.add_task.add_task import add

# 提交任务
res = add.delay(7, 6)

# 打印任务对应的 id
print(res)

2. 延迟任务

# 导入任务
from celery_task.add_task.add_task import add

# 导入时间模块
from datetime import datetime, timedelta

# 对应的是 utc 时间 《我们是东八区,可以通过配置改, 配置可见 3.定时任务 中的代码》
# datetime.utcnow(), 当前 utc 时间
# timedelta(seconds=10), 10 秒后

"""
  如果没有延时执行,一定要把下面这段代码放到函数里,使其能够再次执行即可。
"""
eta = datetime.utcnow() + timedelta(seconds=10)

# 提交任务
# 参数:args: 对应任务函数中的形参。 eta: 需要对应时间对象, 不能只写一个数字
res = add.apply_async(args=(11, 12), eta=eta)

# 打印任务对应的 id
print(res)

3. 定时任务

1. 坑 坑 坑 :使用官方推荐结构时。

  • 使用定时任务,task 中的任务路径可能找不到,需将 task.py 改为 tasks.py

  1. 配置
""" -------------------------------- celery.py 中配置 ----------------------------------- """

# 导入模块
from datetime import timedelta

# 时区, 所有配置字典,默认的
app.conf.timezone = 'Asia/Shanghai'
# 不使用 utc 时间
app.conf.enable_utc = False



# 配置
app.conf.beat_schedule = {

    # 第一个任务
    'send_sms_task': {

        # 任务对应的路径---》路径不要写错
        'task': 'celery_task.task_01.add',

        # 定时任务间隔的时间
        # 'schedule': crontab(hour=8, day_of_week=1),  # 每周一早八点
        # 每隔5秒
        'schedule': timedelta(seconds=5),

        # 对应的参数
        'args': (15, 25),
    },

    # 第二个任务 (同上)
    'add_task': {

        'task': 'celery_task.task_02.add2',

        # 每周三早十二点
        'schedule': crontab(hour=12, minute=10, day_of_week=3),

        # 对应的参数
        'args': (10, 20),
    }
}
  1. 启动(两者顺序可以调换)
   # 启动 beat :提交任务的人
   celery -A celery_task beat -l info

   # 启动 worker :干活的人
   celery -A celery_task worker -l info -P eventlet

六. Celery 监控工具

1. 使用Flower的Webhook功能

Flower支持 Webhook,可以在特定事件发生时(如任务失败)发送HTTP请求到你指定的URL。你可以设置一个接收这些请求的服务,并在接收到请求后发送告警。

# 下载
pip install flower

# 在Flower的配置文件中(或通过命令行参数)配置Webhook:
celery flower --broker=redis://localhost:6379/0 --conf=flowerconfig.py

flowerconfig.py 中配置 Webhook

webhook = {
    'url': 'http://your-webhook-receiver/endpoint',
    'events': ['task-failed']
}

2. 使用Celery的错误处理回调

你可以在任务中定义错误处理回调,当任务失败时,回调函数会被调用,你可以在回调函数中发送告警。

from celery import Celery

app = Celery('myapp', broker='redis://localhost:6379/0')

@app.task(bind=True)
def my_task(self):
    try:
        # 任务逻辑
        pass
    except Exception as exc:
        # 发送告警
        send_alert(f"Task {self.name} failed: {exc}")
        raise

def send_alert(message):
    # 发送告警的逻辑,例如发送电子邮件、短信等
    pass
posted @ 2023-04-27 12:43  codegjj  阅读(25)  评论(0编辑  收藏  举报