Celery 实现异步导出报表

167 阅读5分钟

前提


公司现有产品的报表导出功能存在以下问题:

  1. 导出速度较慢,影响用户体验
  2. 当导出过程中出现异常时,进程会中断且无法监控,缺乏及时的错误告警和重试机制 image.png

异步方案筛选


  1. 使用 export.py 通过 subprocess.popen 执行

    优点

     简单易用,并且报表导出功能模块化
     使用 `Popen` 可以异步执行任务,不会阻塞主线程。
    

    缺点

     处理异常、重试机制、任务状态跟踪等需要手动实现。
     容易引发并发问题
    
  2. 通过 Flask 起一个服务 提供导出报表的 API

    优点

    服务化,易于集成,可以通过 HTTP 请求触发导出任务。
    可以结合数据库来保存任务状态,手动管理任务进度和监控。
    REST API 供外部服务调用。灵活,前端可以轮询导出进度。
    

    缺点

    处理异常、重试机制、任务状态跟踪等需要手动实现。
    需要考虑服务的维护成本。
    
  3. 使用 Celery

    优点

     专业的分布式任务队列,可以异步执行任务,支持多种消息代理(如 Redis、RabbitMQ)。
     天然支持任务监控、任务状态跟踪、错误处理和重试机制。
     可以调度定时任务,并发性能强大。
     Celery 可以通过后台守护进程执行任务,不会阻塞 Flask 主线程。
    

    缺点

     需要配置 Celery 和消息队列
    

总结:导出报表需要快,并且需要 监控导出进程、重试机制 等需求,使用 Celery 是最佳方案。它不仅可以高效地处理异步任务,还能方便地进行任务状态监控、异常处理和自动重试。而且,在 Celery 的支持下,你可以轻松实现任务并发和快速导出,且更好地管理任务的生命周期。

Celery 基础


Celery是什么?

Celery 是一个基于 Python 的分布式异步任务队列框架。

Celery 工作流程

image.png

  1. 任务生产者将任务放入Celery的消息队列中,这可以是任何支持的消息中间件,如RabbitMQ、Redis等。
  2. Celery的工作进程(worker)从消息队列中获取任务。工作进程会不断轮询消息队列,以便获取新的任务。
  3. 当工作进程获取到任务时,它会执行任务所关联的函数或方法,并传递所需的参数。
  4. 任务执行完成后,工作进程将结果返回给任务队列,以便生产者或其他相关方获取结果。

Celery 应用场景

异步任务(async task):解决耗时操作

定时任务(crontab):定时执行某件事情,比如每天数据统计

Celery安装


通过Python包管理平台(PIP)
pip install celery -i https://pypi.tuna.tsinghua.edu.cn/simple pip -U

安装完毕后,可以在命令行看到 Celery工具提供的不同功能的命令行命令。

image.png

Celery任务生产者 - (产生任务) - (客户端) - 将任务写入任务队列(redis)中

  1. 使用Celery客户端库:Celery提供了官方的Python客户端库 celery,可以在外部程序中使用该库来连接到Celery消息队列,并发送任务。通过使用Celery客户端库,外部程序可以直接将任务放入Celery队列中。

    from celery import Celery
    
    # 初始化 Celery 应用,指定 broker(Redis 消息代理)和 backend(任务结果存储)
    app = Celery(
        'tasks',  # 应用名称
        broker='redis://localhost:6379/0',  # 消息代理 URL
        backend='redis://localhost:6379/1'  # 结果存储 URL
    )
    
    # 配置 Celery 应用的其他参数
    app.conf.update(
        result_expires=3600,  # 任务结果过期时间(秒)
    )
    
    
    # 使用 Celery 应用装饰器定义任务
    @app.task
    def add(x, y):
        return x + y
    
    # 使用 delay 方法将任务放入 Celery 队列
    result = add.delay(4, 6)  # 发送异步任务,将 4 和 6 作为参数传递
    
    # 获取任务的 ID
    print(f"Task ID: {result.id}")
    
    # 获取任务的结果(异步执行)
    if result.ready():  # 检查任务是否完成
        print(f"Result: {result.get()}")
    else:
        print("Task is still in progress.")
    
    
  2. 手动发布到消息队列:如果外部程序与Celery使用相同的消息队列(如RabbitMQ、Redis等),则可以直接将任务消息发布到Celery的消息队列中。这样,Celery的工作进程就可以从消息队列中获取到这些任务。 import redis import json

    # 连接到 Redis
    redis client = redis.Redis(host='localhost', port=6379)
    
    # 定义任务消息
    task message ={
        'task': "myapp.tasks.add",
        'args': [4, 6]
    }
    
    # 将任务消息转换为 JSON 字符串
    task json =json.dumps(task message)
    
    #发布任务消息到 Redis 队列
    
    redis_client.lpush('celery', task json)
    
    print('任务已发布到 Redis 队列')
    
  3. 第三方集成:某些框架和工具已经实现了与Celery的集成,使外部程序可以方便地作为任务生产者。例如,Django框架提供了与Celery的集成支持,可以在Django应用程序中使用Celery进行任务处理。

消费者 - (执行任务) - (服务端)- 将任务从队列中拿出来执行


创建异步任务执行文件celery_task

#code='utf-8'
import celery
import time
#存放消费者处理后的结果的地方redis的库1
backend='redis://127.0.0.1:6379/1'
#存放消息队列的地方redis的库2          
broker='redis://127.0.0.1:6379/2'
#创建celery实例          
cel=celery.Celery('test',backend=backend,broker=broker)  

# 只要加上修饰器 celery 的task 下方的方法就会变成celery 的异步任务
@cel.task
def send_email(name):
    print("向%s发送邮件..."%name)
    time.sleep(5)
    print("向%s发送邮件完成"%name)
    return "ok"

注意,先启动 celery 异步任务文件命令执行:

celery -A celery_task worker -l INFO -P eventlet

-P eventlet : 是让celery 开协程

image.png

celery 做了哪些事情尼?

1:连接消息中间件(与redis 创建连接)

2:在消息中间件中创建一个队列并监听这个队列

3:启动多个worker 监听你的任务( tasks)