【问题标题】:Sync & Async Python FastAPI scripts on Google App Engine在 Google App Engine 上同步和异步 Python FastAPI 脚本
【发布时间】:2021-02-13 21:00:44
【问题描述】:

我尝试在本地运行以下py文件script.py

from fastapi import FastAPI
import asyncio
import time

app = FastAPI()

@app.get('/async/')
async def func(text: str):
    await asyncio.sleep(5)
    return {'text': text}


@app.get('/sync/')
async def func(text: str):
    time.sleep(5)
    return {'text': text}

使用uvicorn script:app --reload

10 个并发请求同步端点http://127.0.0.1:8000/sync/?text=test 时,每个调用需要 5 秒并阻塞下一个调用,总时间为 50 秒

当向异步端点http://127.0.0.1:8000/async/?text=test发出10个并发请求时,每个调用需要5秒,总时间也是5秒,因为它是非阻塞的。


我尝试使用 entrypoint: gunicorn -w 4 -k uvicorn.workers.UvicornWorker script:app 将脚本部署到 Google App Engine,并在本地进行了相同的测试,得到了以下结果:

向同步端点发出 10 个并发请求时,总时间为 12 秒,所有 10 个请求都在同一个实例上执行。 (1000 个并发请求在 20 多个实例上耗时 60 秒)

当向异步端点发出 10 个并发请求时,总时间为 5 秒,所有 10 个请求都在同一个实例上执行。 (1000 个并发请求在 20 多个实例上耗时 30 秒)


为什么在 GAE 上,10 个并发请求的同步代码需要 12 秒而不是 50 秒?

我如何在 GAE 上同时为同步端点运行所有 10 个请求,以在 5 秒内将它们全部获取?

【问题讨论】:

  • 你是如何提出这些要求的?
  • 使用k6压力测试(10个虚拟用户10次迭代)

标签: python api google-app-engine asynchronous fastapi


【解决方案1】:

在您的 gunicorn 命令中,您指定了选项 -w 4,这意味着生成了 4 个工作进程 (https://docs.gunicorn.org/en/stable/settings.html#worker-processes)。

即使在阻塞同步代码的情况下,它也是一种启用并发的常用方法。您的 10 个请求被分配给工作人员(10 个请求 * 5 秒 / 4 个工作人员 = 12.5 秒)。

如果你这样运行它:

gunicorn -w 1 -k uvicorn.workers.UvicornWorker script:app

你会经历 50 秒的等待。

但仍然强烈建议生成多个工人。


现在对于同步部分,这里有一个关于 FastAPI 的有趣技术细节。如果您将路由或依赖项定义为async,它将在主进程中运行,无论您是否在内部运行阻塞代码。

这就是您的 /sync 端点阻塞的原因。

但是,如果将其定义为标准的非异步函数,FastAPI 将在线程池中运行它以避免阻塞主进程。

文档:https://fastapi.tiangolo.com/async/?h=technical#path-operation-functions

如果您像这样编写 /sync 端点:

@app.get('/sync/')
def func(text: str):  # Just removed the async keyword
    time.sleep(5)
    return {'text': text}

您会注意到端点不再阻塞主进程。

【讨论】:

  • @mezo 对您有帮助吗? :)
  • 我有一个明确的问题:您是说 默认情况下 FastAPI 在我没有指定任何内容时使用threading 库吗?如果我使用async,我明确表示我将使用asyncio(或希望很快trio),但我也隐含对threading说“不”?我不知道这一点,它极大地帮助我了解什么时候最好付出额外的努力去使用async/await 范式。
  • @MikeWilliamson 我们可以这么说,是的。在内部,非异步函数由 Starlette 处理,它依赖于 asyncio 模块的 run_in_executor 方法:docs.python.org/3/library/… FastAPI 但是强烈鼓励默认使用 async 函数。除非您有大量阻塞 I/O 操作,否则最好将它们作为 async 运行,以避免在线程中运行的小开销。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-30
  • 1970-01-01
  • 2014-04-16
  • 1970-01-01
相关资源
最近更新 更多