架构 Flask 与 FastAPI

Posted

技术标签:

【中文标题】架构 Flask 与 FastAPI【英文标题】:Architecture Flask vs FastAPI 【发布时间】:2020-11-08 14:21:53 【问题描述】:

我一直在修补 Flask 和 FastAPI 以了解它如何充当服务器。 我想知道的主要内容之一是 Flask 和 FastAPI 如何处理来自多个客户端的多个请求。 尤其是当代码存在效率问题(数据库查询时间长)时。

所以,我尝试编写一个简单的代码来理解这个问题。 代码很简单,客户端访问路由时,应用休眠10秒后返回结果。 它看起来像这样:

FastAPI

import uvicorn
from fastapi import FastAPI
from time import sleep
app = FastAPI()

@app.get('/')
async def root():
    print('Sleeping for 10')
    sleep(10)
    print('Awake')
    return 'message': 'hello'

if __name__ == "__main__":
    uvicorn.run(app, host="127.0.0.1", port=8000)

烧瓶

from flask import Flask
from flask_restful import Resource, Api
from time import sleep

app = Flask(__name__)
api = Api(app)

class Root(Resource):
    def get(self):
        print('Sleeping for 10')
        sleep(10)
        print('Awake')
        return 'message': 'hello'

api.add_resource(Root, '/')

if __name__ == "__main__":
    app.run()

应用程序启动后,我尝试通过 2 个不同的 chrome 客户端同时访问它们。 结果如下:

FastAPI

烧瓶

如您所见,对于 FastAPI,代码首先等待 10 秒,然后再处理下一个请求。而对于 Flask,代码会在 10 秒睡眠仍在发生时处理下一个请求。

尽管进行了一些谷歌搜索,但对于这个主题并没有真正的直接答案。 如果有人有任何可以对此有所了解的 cmets,请将它们放在 cmets 中。

您的意见都是值得赞赏的。非常感谢大家的宝贵时间。

编辑 对此的更新,我正在探索更多,并发现了流程管理器的这个概念。例如,我们可以使用进程管理器(gunicorn)运行 uvicorn。通过添加更多的工人,我能够实现像 Flask 这样的东西。然而,仍在测试这个极限。 https://www.uvicorn.org/deployment/

感谢所有离开 cmets 的人!欣赏它。

【问题讨论】:

如果不是使用的框架,而是 WSGI 服务器及其设置,那么关于性能和并发性的最重要部分。 (内置的开发服务器不适合生产。)在我进行的广泛测试中,我注意到它可以区分“负载失败”和“每秒数百个请求”。 【参考方案1】:

这似乎有点有趣,所以我用ApacheBench 进行了一些测试:

烧瓶

from flask import Flask
from flask_restful import Resource, Api


app = Flask(__name__)
api = Api(app)


class Root(Resource):
    def get(self):
        return "message": "hello"


api.add_resource(Root, "/")

FastAPI

from fastapi import FastAPI


app = FastAPI(debug=False)


@app.get("/")
async def root():
    return "message": "hello"

我对 FastAPI 进行了 2 次测试,结果差别很大:

    gunicorn -w 4 -k uvicorn.workers.UvicornWorker fast_api:app uvicorn fast_api:app --reload

所以这里是 5000 请求并发 500 的基准测试结果:

Uvicorn Workers 的 FastAPI

Concurrency Level:      500
Time taken for tests:   0.577 seconds
Complete requests:      5000
Failed requests:        0
Total transferred:      720000 bytes
html transferred:       95000 bytes
Requests per second:    8665.48 [#/sec] (mean)
Time per request:       57.700 [ms] (mean)
Time per request:       0.115 [ms] (mean, across all concurrent requests)
Transfer rate:          1218.58 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    6   4.5      6      30
Processing:     6   49  21.7     45     126
Waiting:        1   42  19.0     39     124
Total:         12   56  21.8     53     127

Percentage of the requests served within a certain time (ms)
  50%     53
  66%     64
  75%     69
  80%     73
  90%     81
  95%     98
  98%    112
  99%    116
 100%    127 (longest request)

FastAPI - 纯 Uvicorn

Concurrency Level:      500
Time taken for tests:   1.562 seconds
Complete requests:      5000
Failed requests:        0
Total transferred:      720000 bytes
HTML transferred:       95000 bytes
Requests per second:    3200.62 [#/sec] (mean)
Time per request:       156.220 [ms] (mean)
Time per request:       0.312 [ms] (mean, across all concurrent requests)
Transfer rate:          450.09 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    8   4.8      7      24
Processing:    26  144  13.1    143     195
Waiting:        2  132  13.1    130     181
Total:         26  152  12.6    150     203

Percentage of the requests served within a certain time (ms)
  50%    150
  66%    155
  75%    158
  80%    160
  90%    166
  95%    171
  98%    195
  99%    199
 100%    203 (longest request)

对于烧瓶

Concurrency Level:      500
Time taken for tests:   27.827 seconds
Complete requests:      5000
Failed requests:        0
Total transferred:      830000 bytes
HTML transferred:       105000 bytes
Requests per second:    179.68 [#/sec] (mean)
Time per request:       2782.653 [ms] (mean)
Time per request:       5.565 [ms] (mean, across all concurrent requests)
Transfer rate:          29.13 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0   87 293.2      0    3047
Processing:    14 1140 4131.5    136   26794
Waiting:        1 1140 4131.5    135   26794
Total:         14 1227 4359.9    136   27819

Percentage of the requests served within a certain time (ms)
  50%    136
  66%    148
  75%    179
  80%    198
  90%    295
  95%   7839
  98%  14518
  99%  27765
 100%  27819 (longest request)

总结果

Flask测试时间:27.827 秒

FastAPI - Uvicorn测试时间:1.562 秒

FastAPI - Uvicorn Workers测试时间:0.577 秒


使用 Uvicorn Workers,FastAPI 比 Flask 快了近 48 倍,这是可以理解的。 ASGI vs WSGI,所以我跑了 1 个并发:

FastAPI - UvicornWorkers测试时间:1.615 秒

FastAPI - Pure Uvicorn测试时间:2.681 秒

Flask测试时间:5.541 秒

我进行了更多测试,以在生产服务器上测试 Flask。

5000 请求 1000 并发

带女服务员的烧瓶

Server Software:        waitress
Server Hostname:        127.0.0.1
Server Port:            8000

Document Path:          /
Document Length:        21 bytes

Concurrency Level:      1000
Time taken for tests:   3.403 seconds
Complete requests:      5000
Failed requests:        0
Total transferred:      830000 bytes
HTML transferred:       105000 bytes
Requests per second:    1469.47 [#/sec] (mean)
Time per request:       680.516 [ms] (mean)
Time per request:       0.681 [ms] (mean, across all concurrent requests)
Transfer rate:          238.22 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    4   8.6      0      30
Processing:    31  607 156.3    659     754
Waiting:        1  607 156.3    658     753
Total:         31  611 148.4    660     754

Percentage of the requests served within a certain time (ms)
  50%    660
  66%    678
  75%    685
  80%    691
  90%    702
  95%    728
  98%    743
  99%    750
 100%    754 (longest request)

Gunicorn 与 Uvicorn 工人

Server Software:        uvicorn
Server Hostname:        127.0.0.1
Server Port:            8000

Document Path:          /
Document Length:        19 bytes

Concurrency Level:      1000
Time taken for tests:   0.634 seconds
Complete requests:      5000
Failed requests:        0
Total transferred:      720000 bytes
HTML transferred:       95000 bytes
Requests per second:    7891.28 [#/sec] (mean)
Time per request:       126.722 [ms] (mean)
Time per request:       0.127 [ms] (mean, across all concurrent requests)
Transfer rate:          1109.71 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0   28  13.8     30      62
Processing:    18   89  35.6     86     203
Waiting:        1   75  33.3     70     171
Total:         20  118  34.4    116     243

Percentage of the requests served within a certain time (ms)
  50%    116
  66%    126
  75%    133
  80%    137
  90%    161
  95%    189
  98%    217
  99%    230
 100%    243 (longest request)

纯 Uvicorn,但这次是 4 个工人 uvicorn fastapi:app --workers 4

Server Software:        uvicorn
Server Hostname:        127.0.0.1
Server Port:            8000

Document Path:          /
Document Length:        19 bytes

Concurrency Level:      1000
Time taken for tests:   1.147 seconds
Complete requests:      5000
Failed requests:        0
Total transferred:      720000 bytes
HTML transferred:       95000 bytes
Requests per second:    4359.68 [#/sec] (mean)
Time per request:       229.375 [ms] (mean)
Time per request:       0.229 [ms] (mean, across all concurrent requests)
Transfer rate:          613.08 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0   20  16.3     17      70
Processing:    17  190  96.8    171     501
Waiting:        3  173  93.0    151     448
Total:         51  210  96.4    184     533

Percentage of the requests served within a certain time (ms)
  50%    184
  66%    209
  75%    241
  80%    260
  90%    324
  95%    476
  98%    504
  99%    514
 100%    533 (longest request)

【讨论】:

你是如何运行烧瓶应用程序的?奇怪的是,对于这样一个简单的示例,每个请求需要 2.7 秒... @marianobianchi 我用app.run() 运行,并发性很高,我认为这并不奇怪。 我就是这么想的。您正在比较像 uvicorn 这样的生产就绪服务器和像 Werkzeug 这样的开发服务器。您应该与使用服务员运行的烧瓶进行比较,这是部署生产就绪烧瓶应用程序的推荐方式:flask.palletsprojects.com/en/1.1.x/tutorial/deploy/… @marianobianchi 太好了,实际上我从 OP 的代码中进行了基准测试,但我将再次与女服务员一起运行测试并用新结果更新问题,谢谢! 准确的分析,我们妥协了,几乎迁移到 FastAPI 以获取我们产品的新版本。我在调试模式下遇到了更多延迟,特别是在反向代理后面使用时。【参考方案2】:

您在async 端点中使用time.sleep() 函数。 time.sleep() 是阻塞的,不应该在异步代码中使用。您应该使用的可能是asyncio.sleep() 函数:

import asyncio
import uvicorn
from fastapi import FastAPI
app = FastAPI()

@app.get('/')
async def root():
    print('Sleeping for 10')
    await asyncio.sleep(10)
    print('Awake')
    return 'message': 'hello'

if __name__ == "__main__":
    uvicorn.run(app, host="127.0.0.1", port=8000)

这样,每个请求将需要大约 10 秒才能完成,但您将能够同时处理多个请求。

一般来说,异步框架为标准库中的所有阻塞函数(睡眠函数、IO 函数等)提供了替换。您应该在编写异步代码和(可选)await 时使用这些替换。

一些非阻塞框架和库(例如 gevent)不提供替代品。他们代替标准库中的猴子补丁函数,使它们成为非阻塞的。但据我所知,对于较新的异步框架和库,情况并非如此,因为它们旨在允许开发人员使用 async-await 语法。

【讨论】:

【参考方案3】:

我认为您在 FastAPI 中阻塞了一个事件队列,这是一个异步框架,而在 Flask 中,请求可能每个都在新线程中运行。将所有 CPU 绑定任务移动到单独的进程或在您的 FastAPI 示例中只是在事件循环上休眠(不要在此处使用 time.sleep)。在 FastAPI 中异步运行 IO 绑定任务

【讨论】:

【参考方案4】:

为什么代码很慢

阻塞操作将停止运行任务的事件循环。当您调用sleep() 函数时,所有任务(请求)都在等待它完成,从而扼杀了异步代码执行的所有好处。

要理解为什么这段代码比较错误,我们应该更好地理解异步代码在 Python 中是如何工作的,并且对 GIL 有一些了解。并发和异步代码在 FastAPI 的docs 中有很好的解释。

@Asotos 描述了为什么你的代码很慢,是的,你应该使用协程进行 I/O 操作,因为它们会阻塞事件循环的执行(sleep() 是阻塞操作)。合理地建议使用异步函数,这样事件循环就不会被阻塞,但目前,并非所有库都有异步版本。

没有async函数和asyncio.sleep的优化

如果您不能使用库的异步版本,您可以简单地将路由函数定义为简单的def 函数,而不是async def

如果路由函数定义为同步(def),FastAPI 会在外部线程池中智能调用该函数,不会阻塞有事件循环的主线程,不使用你的 benchmark 会好很多await asyncio.sleep()。在this 部分有详细解释。

解决方案

from time import sleep

import uvicorn
from fastapi import FastAPI


app = FastAPI()

@app.get('/')
def root():
    print('Sleeping for 10')
    sleep(10)
    print('Awake')
    return 'message': 'hello'

if __name__ == "__main__":
    uvicorn.run(app, host="127.0.0.1", port=8000)

顺便说一句,如果由于GIL 而在线程池中运行的操作受 CPU 限制(例如繁重的计算),您将不会获得很多好处。 CPU 密集型任务必须在单独的进程中运行。

【讨论】:

以上是关于架构 Flask 与 FastAPI的主要内容,如果未能解决你的问题,请参考以下文章

Flask-Migrate 首次迁移时未检测到架构更改

Flask 与 Django 框架对比

Flask web开发01:初识 Flask

Serverless 与 Flask 框架结合进行 Blog 开发

Serverless 与 Flask 框架结合进行 Blog 开发

Django框架与Flask框架的区别