使用FastAPI和Celery的异步任务

3,618 阅读7分钟

如果一个长期运行的进程是你的应用程序工作流程的一部分,而阻断响应,你应该在后台处理它,在正常的请求/响应流程之外。

也许你的网络应用需要用户在注册时提交一个缩略图(可能需要调整大小)并确认他们的电子邮件。如果你的应用程序直接在请求处理程序中处理图片并发送确认邮件,那么终端用户将不得不在页面加载或更新之前不必要地等待它们都完成处理。相反,你会想把这些处理传递给一个任务队列,让一个单独的工作进程来处理,这样你就可以立即向客户端发送一个响应。然后,终端用户可以在处理过程中在客户端做其他事情。你的应用程序也可以自由地响应其他用户和客户端的请求。

为了实现这一目标,我们将引导你完成设置和配置Celery和Redis的过程,以便在FastAPI应用中处理长期运行的进程。我们还将使用Docker和Docker Compose将一切联系起来。最后,我们将看看如何用单元和集成测试来测试Celery任务。

目标

在本教程结束时,你将能够。

  1. 将Celery集成到FastAPI应用中并创建任务。
  2. 用Docker容器化FastAPI、Celery和Redis。
  3. 用单独的工作进程在后台运行进程。
  4. 将Celery日志保存到一个文件中。
  5. 设置Flower来监控和管理Celery作业和工作者。
  6. 用单元和集成测试来测试Celery任务。

后台任务

同样,为了改善用户体验,长期运行的进程应该在正常的HTTP请求/响应流程之外,在后台进程中运行。

例子。

  1. 运行机器学习模型
  2. 发送确认邮件
  3. 刮削和抓取
  4. 分析数据
  5. 处理图像
  6. 生成报告

当你建立一个应用程序时,试着区分那些应该在请求/响应生命周期中运行的任务,如CRUD操作,和那些应该在后台运行的任务。

值得注意的是,你可以利用FastAPI的BackgroundTasks类,它直接来自Starlette,在后台运行任务。

比如说。

那么,什么时候你应该使用Celery而不是BackgroundTasks

  1. CPU密集型任务。Celery应该用于执行繁重的后台计算的任务,因为BackgroundTasks 在为你的应用程序的请求服务的同一个事件循环中运行。
  2. **任务队列。**如果你需要一个任务队列来管理任务和工作者,你应该使用Celery。通常你想检索一个任务的状态,然后根据状态执行一些行动--例如,发送一封错误邮件,启动一个不同的后台任务,或者重试该任务。Celery为你管理这一切。

工作流程

我们的目标是开发一个FastAPI应用程序,与Celery一起工作,处理正常请求/响应周期之外的长期运行的进程。

  1. 最终用户通过向服务器端发出POST请求来启动一个新的任务。
  2. 在路由处理程序中,一个任务被添加到队列中,任务ID被送回客户端。
  3. 使用AJAX,客户端继续轮询服务器以检查任务的状态,而任务本身正在后台运行。

fastapi and celery queue user flow

项目设置

fastapi-celeryrepo中克隆出基本项目,然后将v1标签检出到主分支。

由于我们总共需要管理三个进程(FastAPI、Redis、Celery worker),我们将使用Docker来简化我们的工作流程,将它们连接起来,使它们都能从一个终端窗口用一个命令运行。

在项目根目录下,创建镜像并启动Docker容器。

一旦构建完成,导航到http://localhost:8004。

fastapi project

确保测试也能通过。

在继续前行之前,快速浏览一下项目结构。

触发一个任务

project/templates/home.html中设置了一个onclick 事件处理程序,监听按钮的点击。

onclick 调用project/static/main.js中的 ,它向服务器发送一个AJAX POST请求,任务类型适当。, , 或 。handleClick 1 2 3

在服务器端,project/main.py中已经配置了一个路由来处理该请求。

现在,有趣的部分来了--为Celery布线!

Celery的设置

首先在requirements.txt文件中加入Celery和Redis。

本教程使用Celery v4.4.7,因为Flower不支持Celery 5

Celery使用一个消息代理--RabbitMQRedisAWS简单队列服务(SQS)--以促进Celery工作者和Web应用程序之间的通信。消息被添加到代理,然后由工作者处理。一旦完成,结果将被添加到后端。

Redis将被用作代理和后端。在docker-compose.yml文件中添加Redis和Celery工作者,像这样。

请注意celery worker --app=worker.celery --loglevel=info

  1. celery worker 是用来启动Celery工作者
  2. --app=worker.celery 运行Celery应用程序(我们很快就会定义)。
  3. --loglevel=info日志级别设为info

接下来,在 "项目 "中创建一个名为worker.py的新文件。

在这里,我们创建了一个新的Celery实例,并使用任务装饰器,定义了一个新的Celery任务函数,名为create_task

请记住,该任务本身将由 Celery 工作器执行。

触发一个任务

更新路由处理程序来启动任务,并以任务ID来响应。

不要忘记导入该任务。

构建镜像并启动新的容器。

要触发一个新的任务,运行。

你应该看到类似的东西。

任务状态

再回头看看客户端的handleClick 功能。

当原始AJAX请求的响应回来后,我们再继续每秒钟用任务ID调用getStatus()

如果响应成功,就会在DOM上的表格中添加新的一行。

更新get_status 路由处理程序以返回状态。

输入AsyncResult

更新容器。

触发一个新的任务。

然后,从响应中抓取task_id ,并调用更新的端点来查看状态。

也可以在浏览器中测试一下。

fastapi, celery, docker

Celery Logs

docker-compose.yml中更新worker 服务,以便Celery日志被转储到一个日志文件中。

在 "项目 "中添加一个名为 "日志 "的新目录。然后,在这个新创建的目录中添加一个名为celery.log的新文件。

更新。

你应该看到日志文件在本地填满了,因为我们设置了一个卷。

Flower仪表板

Flower是Celery的一个轻量级、实时、基于网络的监控工具。你可以监控当前运行的任务,增加或减少工作池,查看图表和一些统计数据,仅此而已。

把它添加到requirements.txt中。

然后,在docker-compose.yml中添加一个新服务。

测试一下吧。

导航到http://localhost:5556,查看仪表板。你应该看到一个工作者已经准备好了。

flower dashboard

再启动几个任务来全面测试仪表板。

flower dashboard

尝试添加更多的工作者,看看这对事情有什么影响。

测试

让我们从最基本的测试开始。

project/tests/test_tasks.py中添加上述测试案例,然后添加以下导入。

单独运行该测试。

它应该需要一分钟左右的时间来运行。

值得注意的是,在上面的断言中,我们使用了.run 方法(而不是.delay )来直接运行任务,而没有使用 Celery worker。

想模拟.run 方法以加快速度吗?

输入。

测试。

快多了!

一个完整的集成测试怎么样?

请记住,这个测试使用的是开发中使用的相同的代理和后端。你可能想实例化一个新的Celery应用来测试。

添加导入。

确保测试通过。

总结

这已经是一个关于如何配置Celery以在FastAPI应用程序中运行长期运行任务的基本指南。你应该让队列处理任何可能阻塞或减慢面向用户的代码的进程。

Celery也可以用来执行可重复的任务,并将复杂的、资源密集型的任务分解,以便将计算工作量分布在一些机器上,以减少(1)完成的时间和(2)处理客户端请求的机器上的负载。

repo 中抓取代码。