架构 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的主要内容,如果未能解决你的问题,请参考以下文章
Serverless 与 Flask 框架结合进行 Blog 开发