Celery

222 阅读7分钟

概述

Celery是一个简单灵活且可靠的、处理大量消息的分布式系统,专注于实时处理的异步任务队列,同时也支持任务调度。

  • 分布式系统:一个系统应用的相关组件,架构在不同的服务器上,不同组件之间通过消息通信的方式来实现协调工作。

  • 异步请求: 同步请求的每一个步骤按顺序进行,下一个步骤永远要等待它前面的所有步骤完成后才能进行。异步请求发出后,不等待返回结果而继续执行别的业务操作;异步请求的结果会被存入某一个数据库中,当主进程需要请求结果时去数据库取。

架构

Celery的架构由三部分组成,分别是消息中间件(message broker),执行任务单元(worker)和任务执行结果存储(task result store)组成。

  • 消息中间件:Celery本身不停工该消息服务,但是可以方便的和第三方提供的消息中间件集成。包括RebbitMQ, Redis等等。
  • 任务执行单元:Worker是Celery提供的任务执行单元,worker并发的运行在分布式的系统节点中。
  • 任务结果存储:用来储存worker执行的任务结果,Celery支持以不同分时存储任务的结果,包括AMQP, Redis等。另外,Celery还支持不同的并发(Prefork, Eventlet, gevent, threads/signle threaded)和序列化(pickle, JSON, yaml, msgpack.zlib, bzip2 compression, Cryptographic message signing)的手段。

Django              RebbitMQ\Redis         Celery               Redis/...
+++++++++++           ++++++++++         ++++++++++            +++++++++++
+   User  +---------->+        +-------->+ worker +----------->+         +
+++++++++++           +        +         ++++++++++            +   task  +
                      +        +                               +         +
                      + broker +         ++++++++++            +  result +
                      +        +-------->+ worker +----------->+         +
                      +        +         ++++++++++            +  store  +
                      +        +            .                  +         +
                      +        +            .                  +++++++++++
                      ++++++++++            .

使用场景

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

  • 异步任务:将耗时操作任务提交给Celery去异步执行,比如发送短信/邮件、消息推送、音频处理等等。
  • 定时任务:定时执行某件事情,比如每天数据统计。

Celery优点

  • 简单: Celery的使用和维护都非常简单,并且不需要配置文件。
  • 高可用:worker和client会在网络链接丢失或失败时自动进行充实。
  • 快速:单个celery进程可以每分钟处理百万级别的任务,并且只需要毫秒级的往返延迟。
  • 灵活:Celery几乎每个部分都可以扩展使用,自定义池实、序列化、压缩方案、日志记录、调度器、消费者、生产者、broker传输等等。

安装Celery

pip install -U Celery

使用Redis作为broker时,再安装一个celery-with-redis

Celery执行异步任务

基本使用

创建项目:celerypro 创建异步任务执行文件:celery_task.py(消费者)

import celery
import time

backend='redis://127.0.0.1:6379/1'  # task result store, 储存异步结果
broker='redis://127.0.0.1:6379/2'  # 消息中间件
# redis会细分成16个库,可以从1~15自由选择

cel = celery.Celery('test', backend=backend, broker=broker)  # 'test'是celery的名字,随便起的

@cel.task
def send_emaul(name):
    print(f'向{name}发送邮件...)
    time.sleep(5)
    print(f'向{name}发送邮件完毕)
    return "OK"

异步任务执行监听命令:

celery worker -A celery_task -1 info

celery_task是指定的文件名称。info是日志。

创建执行任务文件:produce_task.py (目的是向指定的消息中间件插入数据)

from celery_task import send_email  # 从消费者导入异步任务函数

result = send_email.delay("yuan")  # delay()方法帮助链接消息中间件队列
                                   # 插入异步任务函数的名字send_email和异步任务函数的参数"yuan"
print(result.id)
result2 = send_email.delay("alex")
print(result2.id)

异步请求的结果不会返回,必须用id值从指定的数据库取结果。

创建获取异步任务结果的程序:result.py

from celery.result import AsyncResult
from celery_task import cel

async_result=AsyncResult(id='***************', app=cel)  # id是从produce_task.py中获取的

if async_result.successful():
    result = async_result.get()
    print(result)
elif async_result.failed():
    print('执行失败')
elif async_result.status == 'PENDING':
    print('任务等待中')
elif async_result.status == 'RETRY':
    print('任务异常,正在重试')
elif async_result.status == 'STARTED':
    print('任务执行中')

多任务结构

消费者(celery_tasks)不再是单一的脚本程序,而是一个package包

\celery_tasks----
                 |
                 -----__init__.py
                 |
                 -----celery.py  # celery配置文件
                 |
                 -----task01.py
                 |
                 -----task02.py

配置文件celery.py:

from celery import Celery

cel = Celery('celery_demo',  
             broker='redis://127.0.0.1:6379/1', 
             backend='redis://127.0.0.1:6379/2',
             # 包含以下两个任务文件,去相应的py文件中找任务,对多个任务做分类
             include=['celery_tasks.task01', 
                      'celery_tasks.task01', 
                      ]
             )
             
# 时区
cel.conf.timezone = 'Asia/Shanghai'
# 是否使用UTC
cel.conf.enable_utc = False             

在task01.py中构建任务函数

# task01
import time
from celery_tasks.celery import cel

@cel.task
def send_email(name):
    print(f"向{name}发送邮件")
    time.sleep(5)
    return "邮件发送完成"  # 会出现在日志中

在task02.py中构建任务函数

# task02
import time
from celery_tasks.celery import cel

@cel.task
def send_msg(name):
    print(f"向{name}发送短信")
    time.sleep(5)
    return "短信发送完成"

启动woker:

celery worker -A celery_tasks -I info -P eventlet  # eventlet协程

添加任务(执行produce_task.py)

from celery_tasks.task01 import send_email  # 从消费者导入异步任务函数
from celery_tasks.task01 import send_msg

result = send_email.delay("yuan")  # delay()方法帮助链接消息中间件队列
                                   # 插入异步任务函数的名字send_email和异步任务函数的参数"yuan"
print(result.id)
result2 = send_msg.delay("alex")
print(result2.id)

检查任务执行结果(执行check_result.py)

from celery.result import AsyncResult
from celery_task import cel

async_result=AsyncResult(id='***************', app=cel)  # id是从produce_task.py中获取的

if async_result.successful():
    result = async_result.get()
    print(result)
    # result.forget()  将结果删除
    # async.revoke(terminate=True)  无论任务现在是什么进度,都终止任务
    # async.revoke(terminate=False)  如果任务还没有开始执行, 终止任务
elif async_result.failed():
    print('执行失败')
elif async_result.status == 'PENDING':
    print('任务等待中')
elif async_result.status == 'RETRY':
    print('任务异常,正在重试')
elif async_result.status == 'STARTED':
    print('任务执行中')

celery简单结构下执行定时任务

celery_task.py:

import celery
import time

backend='redis://127.0.0.1:6379/1'  
broker='redis://127.0.0.1:6379/2'  

cel = celery.Celery('test', backend=backend, broker=broker)  # 'test'是celery的名字,随便起的

@cel.task  # 由装饰器cel.task装饰的任务,既可以是异步任务,也可以是定时任务,取决于怎么调用
def send_emaul(name):
    print(f'向{name}发送邮件...)
    time.sleep(5)
    print(f'向{name}发送邮件完毕)
    return "OK"

produce_task.py:

from celery_task import send_email
from datetime import datetime

# 方法一
v1 = datetime(2024, 3, 20, 10, 13. 00)  # 本地时间
v2 = datetime.utcfromtimestamp(v1.timestamp())  # 转换成国标时间
result = send_email.apply_async(args=["egon", ], eta=v2)  # 调用定时方法用apply_async
                                # args用来传参数
                                # eta传时间,不传就是异步任务
print(result.id)

# 方法二
ctime = datetime.now()
utc_ctime = datetime.utcfromtimestamp(ctime.timestamp())
from datetime import timedelta
time_delay = timedelta(seconds=10)
task_time = utc_ctime + time_delay  # 添加一个固定时差
result = send_email.apply_async(args=["egon", ], eta=v2)

celery多目录下执行定时任务

配置文件celery.py:

from celery import Celery
from datetime import timedelta

cel = Celery('celery_demo',  
             broker='redis://127.0.0.1:6379/1', 
             backend='redis://127.0.0.1:6379/2',
             # 包含以下两个任务文件,去相应的py文件中找任务,对多个任务做分类
             include=['celery_tasks.task01', 
                      'celery_tasks.task01', 
                      ]
             )
             
# 时区
cel.conf.timezone = 'Asia/Shanghai'
# 是否使用UTC
cel.conf.enable_utc = False       

cel.conf.beat_schedule = {  # beat_schedule是定时任务相关的调度器
    'add-every-10-seconds': {  # 每一个键值对都是一个定时任务,键是定时任务的名称,
        # 执行task1下的send_email函数
        'task': 'celery_tasks.task01.send_email',
        # 每十秒执行一次
        'schedule': timedelta(seconds=10),
        # 传递参数
        'args': ('john', )
    }
}

在Django中使用Celery

生产者不是一个简单的python脚本,而是Django应用程序。

项目根目录创建celery包,目录结构如下:

mycelery/
|--config.py  # 配置文件: broker_url + result_backend
|--__init__.py
|--main.py
|--sms/  # 异步任务模块,名字可自选
   |--__init__.py
   |--tasks.py  # 定义异步/定时任务的文件

主程序main.py:

import os
from celery import Celery
# 创建celery实例对象
app = Celery("sms")  # broker和backend放在config文件

# 把celery和django进行组合,识别和加载django的配置文件
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'celeryPros.settings.dev')

# 通过app对象加载celery的配置文件
app.config_from_object("mycelery.config")

# 加载任务
# 参数必须是一个列表,里面的每一个任务都是人物的路径名称
app.autodiscover_tasks(["mycelery.sms", ])  # 实际上找到的是mycelery.sms.tasks.py

启动Celery的命令:

celery -A mycelery.main worker --loglevel=info

任务文件tasks.py:

from mycelery.main import app  # Celery对象
import time 

import logging
log = logging.getLogger("django")

@app.task
def send_sms(mobile):
    print(f"向手机号{mobile}发送短信")
    time.sleep(5)
    
    return "send_sms OK"

在视图中执行任务

from my.celery.sms.tasks import send_sms

def test(request):
    # 异步任务
    send_sms.delay("18612345678")
    
    return HttpResponse("OK")