为了账号安全,请及时绑定邮箱和手机立即绑定

FastAPI项目中用Celery和RabbitMQ处理耗时任务,提升服务器响应速度

大家好,今天我想分享一下最近使用Celery + RabbitMQ与FastAPI的一些经历。最近我在一个与音频分析相关的项目上工作。在这个项目中,有些任务需要一点时间来完成。因此,当客户端向服务器发送请求时,服务器会暂时忙于处理这些任务。因此,响应客户端的请求会需要一些时间。我搜索了如何在后台运行任务来避免这种情况。最后,我发现了一个解决方案,Celery,一个分布式任务队列。在这篇文章中,我将描述我如何使用Celery来实现我的目标。

Celery 是一个带有实时处理的任务队列。它还支持任务调度。简单来说,当我的服务器收到一个耗时任务的请求时,我可以将该任务添加到任务队列中,在后台执行。Celery 会跟踪任务的状态,无论任务是等待、进行中、成功等。Celery 需要一个叫做 消息代理 的服务。当我的服务器(主应用程序)收到任务请求时,它需要在任务队列中等待,直到有可用的任务执行者。当有工作者可用时,它将从消息队列中取出任务并在后台执行。RabbitMQ 是 Celery 的默认消息代理。其他解决方案包括 Redis、Amazon SQS 和 Apache Kafka。我选择了 RabbitMQ,因为我想要尽可能简单地实现。这个动画视频将解释它是如何工作的。

Celery 是怎么工作的

实施

首先,我们来设置RabbitMQ。我使用Windows环境开发我的项目。我使用Docker镜像来设置RabbitMQ。如果你想的话,当然也可以直接安装RabbitMQ,而不是用Docker。

运行一个Docker容器,将宿主机的15672和5672端口映射到容器内的相应端口,启动带有管理界面的RabbitMQ服务。

docker run -p 15672:15672 -p 5672:5672 rabbitmq:3-management

当Docker容器启动时,你可以通过浏览器在127.0.0.1:5672 访问并登录到RabbitMQ管理界面,用户名和密码都是guest。

RabbitMQ 仪表盘

现在我们来安装并配置Celery。安装下面这些库。我使用python-dotenv库来管理.env文件中的机密信息。

    pip install celery python-dotenv

安装Celery和python-dotenv库。

我的 FastAPI 项目采用了类似的文件夹组织。

以下是一些主要文件和目录结构:
app/  # app主目录
│
├── config/  # 配置目录
│   ├── __init__.py  # 初始化文件
│   └── celery_config.py  # Celery配置文件
│
├── tasks/  # 任务目录
│   ├── __init__.py  # 初始化文件
│   └── celery_tasks.py  # Celery任务文件
│
├── __init__.py  # 初始化文件
├── main.py  # 主程序文件
├── .env  # 环境变量配置文件
└── requirements.txt  # 项目依赖列表

首先,在 .env 文件中创建我们的环境变量。Celery 需要两样东西。第一样是消息代理的 URL 地址。在我的例子中,这是 RabbitMQ 服务器的 URL。(请将 <USERNAME><PASSWORD> 替换为您的用户名和密码,默认用户是 guest。)第二样是结果后端。这里 Celery 存储任务执行的结果。完成任务后,我们就可以查看结果。

    CELERY_BROKER_URL=amqp://<USERNAME>:<PASSWORD>@localhost:5672//  # 设置Celery使用的AMQP代理
    CELERY_RESULT_BACKEND=rpc://  # 指定Celery的结果后端

好的。行了,我们现在来创建我们的 Celery 应用程序,然后配置一下它。我为此创建了一个叫 celery_config.py 的文件。

    # celery_config.py
    # 用于定义 Celery 任务的配置文件。
    import os
    from celery import Celery
    from dotenv import load_dotenv

    load_dotenv()  # 加载环境变量以获取配置信息

    celery_app = Celery(__name__, broker=os.getenv("CELERY_BROKER_URL"), backend=os.getenv("CELERY_RESULT_BACKEND"))

    celery_app.conf.update(
        imports=['app.tasks.celery_tasks'],  # 指向您的 Celery 任务文件的路径
        broker_connection_retry_on_startup=True,  # 启动时重试 broker 连接
        task_track_started=True
    )

我创建了一个单独的文件来定义 Celery 任务,但这并不是必要的。你可以直接在这个文件里定义你的任务。如果你这样做了,就无需再添加 imports=['app.tasks.celery_tasks'] 这行了。这样配置就完成了。

    # celery_config.py
    # 此文件用于配置 celery 任务定义。
    import os
    from celery import Celery
    from dotenv import load_dotenv

    load_dotenv()  # 加载环境变量

    celery_app = Celery(__name__, broker=os.getenv("CELERY_BROKER_URL"), backend=os.getenv("CELERY_RESULT_BACKEND"))

    celery_app.conf.update(
        broker_connection_retry_on_startup=True,  # 启动时 broker 连接重试 = 真,
        task_track_started=True  # 任务开始跟踪 = 真
    )

现在,我们来创建一些celery任务。我创建了一个celery_tasks.py文件,并在里面定义了任务。我们使用装饰器来定义这些celery任务。

    # celery_tasks.py
    import asyncio
    from app.config.celery_config import celery_app

    @celery_app.task
    def my_task(x, y):
        ans = x + y
        print(ans)
        return ans

    async def my_async_task(x, y):
        await asyncio.sleep(3)
        ans = x + y
        print(ans)
        return ans

    @celery_app.task
    def my_second_task(x, y):
        result = asyncio.run(my_async_task(x, y))
        return result

这样你可以在配置文件中定义任务,而不是使用单独的文件。

    # celery_config.py
    # 在此文件中配置celery任务。
    import os
    import asyncio
    from celery import Celery
    from dotenv import load_dotenv

    load_dotenv()  # 加载环境变量

    celery_app = Celery(__name__, broker=os.getenv("CELERY_BROKER_URL"), backend=os.getenv("CELERY_RESULT_BACKEND"))

    celery_app.conf.update(
        broker_connection_retry_on_startup=True,
        task_track_started=True  # 从任务开始追踪
    )

    @celery_app.task
    def my_task(x, y):
       ans = x + y
       print(ans)
       return ans

    async def my_async_task(x, y):
       await asyncio.sleep(3)
       ans = x + y
       print(ans)
       return ans

    @celery_app.task
    def my_second_task(x, y):
       result = asyncio.run(my_async_task(x, y))
       return result

现在,我们可以使用来自路由处理器的任务。这是一个示例用的路由处理器。在这里,我创建了一个 GET 端点。在这个端点处理程序中,我使用了 Celery 任务。

    从 fastapi 导入 FastAPI 作为 FastAPI  

    从 app.tasks.celery_tasks 导入 my_task 作为 my_task  

    app = FastAPI()  

    @app.get("/run")  
    def handle_run():  
       task_response = my_task.delay(5, 6)  
       return {"message": "任务执行已开始"}  

    如果 __name__ == '__main__':  
        uvicorn.run(app, port=8080)

好的。我们的编码部分结束了。现在,让我们运行我们的 Celery 工作者。这个 Celery 工作者将会执行 RabbitMQ 消息队列中的任务。如果你没有启动 RabbitMQ 服务器的话,在启动 Celery 工作者之前,请先启动 RabbitMQ 服务。

    celery --app app.config.celery_config.celery_app worker --loglevel=info --pool=solo

如果 celery 顺利运行,你可以在终端看到这样的输出。

香菜的命令行输出

在上面的图中,在 [任务] 部位,我们可以看到 celery 在 celery_tasks.py 文件中定义的任务。当任务队列接收到新任务后,celery 就会执行任务。现在我们来启动 FastAPI 服务器。

    uvicorn app.main:app --port 8000

运行上述命令以启动应用程序。

在 Swagger 文档页上,我们来试一下这个端点。发出请求后,如果检查 celery 终端,可以看到类似这样的结果。

它显示任务已接收并开始执行。执行完成后,它显示任务成功。这意味着我们的任务并不是由FastAPI服务器来执行的。我们所有的任务都是由celery工作者来完成的。

任务监控

我们可以使用花来监控celery任务及其工作者,现在我们来安装它。

运行以下命令来安装flower:

pip install flower

接着在你的本地电脑上运行flower程序

    celery flower --app app.config.celery_config.celery_app --broker:amqp://localhost//

使用 celery flower 命令启动 celery 花瓣监控工具,其中 --app 参数指定了 celery 应用的配置路径,而 --broker 参数指定了消息代理的地址。

你可以在网页浏览器中查看花卉监控工具。访问http://localhost:5555/

Flower

这是我如何在我的FastAPI项目中使用Celery和RabbitMQ的方法。我想你已经理解了这里的情况。这是最简单的方法之一。如果你想的话,你可以创建更多的任务队列。还有很多可以调整的设置。你可以查看他们的文档。你可以利用WebSocket来通知客户端任务是否完成。就这样吧。下次博客见喽。祝你编程愉快!

栈学社 🎓

感谢您一直读到最后,临走前还想说几句:

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消