前提
公司现有产品的报表导出功能存在以下问题:
- 导出速度较慢,影响用户体验;
- 当导出过程中出现异常时,进程会中断且无法监控,缺乏及时的错误告警和重试机制
异步方案筛选
-
使用
export.py通过subprocess.popen执行优点:
简单易用,并且报表导出功能模块化 使用 `Popen` 可以异步执行任务,不会阻塞主线程。缺点:
处理异常、重试机制、任务状态跟踪等需要手动实现。 容易引发并发问题 -
通过 Flask 起一个服务 提供导出报表的 API
优点:
服务化,易于集成,可以通过 HTTP 请求触发导出任务。 可以结合数据库来保存任务状态,手动管理任务进度和监控。 REST API 供外部服务调用。灵活,前端可以轮询导出进度。缺点:
处理异常、重试机制、任务状态跟踪等需要手动实现。 需要考虑服务的维护成本。 -
使用 Celery
优点:
专业的分布式任务队列,可以异步执行任务,支持多种消息代理(如 Redis、RabbitMQ)。 天然支持任务监控、任务状态跟踪、错误处理和重试机制。 可以调度定时任务,并发性能强大。 Celery 可以通过后台守护进程执行任务,不会阻塞 Flask 主线程。缺点:
需要配置 Celery 和消息队列
总结:导出报表需要快,并且需要 监控导出进程、重试机制 等需求,使用 Celery 是最佳方案。它不仅可以高效地处理异步任务,还能方便地进行任务状态监控、异常处理和自动重试。而且,在 Celery 的支持下,你可以轻松实现任务并发和快速导出,且更好地管理任务的生命周期。
Celery 基础
Celery是什么?
Celery 是一个基于 Python 的分布式异步任务队列框架。
Celery 工作流程
- 任务生产者将任务放入Celery的消息队列中,这可以是任何支持的消息中间件,如RabbitMQ、Redis等。
- Celery的工作进程(worker)从消息队列中获取任务。工作进程会不断轮询消息队列,以便获取新的任务。
- 当工作进程获取到任务时,它会执行任务所关联的函数或方法,并传递所需的参数。
- 任务执行完成后,工作进程将结果返回给任务队列,以便生产者或其他相关方获取结果。
Celery 应用场景
异步任务(async task):解决耗时操作
定时任务(crontab):定时执行某件事情,比如每天数据统计
Celery安装
通过Python包管理平台(PIP)
pip install celery -i https://pypi.tuna.tsinghua.edu.cn/simple pip -U
安装完毕后,可以在命令行看到 Celery工具提供的不同功能的命令行命令。
Celery任务生产者 - (产生任务) - (客户端) - 将任务写入任务队列(redis)中
-
使用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.") -
手动发布到消息队列:如果外部程序与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 队列') -
第三方集成:某些框架和工具已经实现了与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 开协程
celery 做了哪些事情尼?
1:连接消息中间件(与redis 创建连接)
2:在消息中间件中创建一个队列并监听这个队列
3:启动多个worker 监听你的任务( tasks)