架构 Flask 与 FastAPI
Architecture Flask vs FastAPI
我一直在研究 Flask 和 FastAPI 以了解它如何充当服务器。
我想知道的主要事情之一是 Flask 和 FastAPI 如何处理来自多个客户端的多个请求。
特别是当代码存在效率问题时(数据库查询时间长)。
所以,我试着编写了一个简单的代码来理解这个问题。
代码很简单,客户端访问路由时,应用休眠10秒才returns出结果。
它看起来像这样:
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 秒睡眠仍在发生时处理下一个请求。
尽管进行了一些谷歌搜索,但关于这个主题并没有真正直接的答案。
如果有人对此有任何意见,请在评论中提出。
非常感谢您的意见。非常感谢大家的宝贵时间。
编辑
关于此的更新,我正在探索更多并发现了流程管理器的概念。例如,我们可以使用进程管理器 (gunicorn) 运行 uvicorn。通过添加更多工人,我能够实现类似 Flask 的功能。然而,仍在测试其极限。
https://www.uvicorn.org/deployment/
感谢所有留下评论的人!欣赏一下。
我认为您在异步框架 FastAPI 中阻塞了一个事件队列,而在 Flask 中请求可能 运行 每个都在新线程中。将所有 CPU 绑定任务移动到单独的进程中,或者在您的 FastAPI 示例中仅在事件循环中休眠(不要在此处使用 time.sleep)。在 FastAPI 中 运行 IO 异步绑定任务
这看起来有点有趣,所以我 运行 用 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 请求的基准测试结果:
FastAPI 与 Uvicorn Workers
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)
对于 Flask:
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 快近 48x,这是非常可以理解的。 ASGI vs WSGI,所以我 运行 有 1 个并发:
FastAPI - UvicornWorkers:测试时间:1.615 秒
FastAPI - 纯 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)
纯独角兽,不过这次是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)
您正在 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)不提供替代品。他们取而代之的是标准库中的猴子补丁函数,使它们成为非阻塞的。据我所知,对于较新的异步框架和库来说情况并非如此,因为它们旨在允许开发人员使用异步等待语法。
为什么代码很慢
阻塞操作将停止您的事件循环运行任务。当您调用 sleep()
函数时,所有任务(请求)都在等待它完成,从而扼杀了异步代码执行的所有好处。
要理解为什么这段代码比较错误,我们应该更好地理解异步代码在Python中是如何工作的,并且对GIL有一些了解。并发和异步代码在 FastAPI 的 docs 中有很好的解释。
@Asotos 已经描述了为什么你的代码很慢,是的,你应该使用协程进行 I/O 操作,因为它们会阻止事件循环执行(sleep()
是一个阻塞操作)。合理地建议使用异步函数,这样事件循环就不会被阻塞,但目前,并不是所有的库都有异步版本。
没有 async
函数和 asyncio.sleep
的优化
如果您不能使用库的异步版本,您可以简单地将路由函数定义为简单的 def
函数,而不是 async def
。
如果路由函数定义为同步(def
),FastAPI会在外部线程池中智能调用该函数,不会阻塞带事件循环的主线程,你的benchmarks会是不使用 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 和 FastAPI 如何处理来自多个客户端的多个请求。
特别是当代码存在效率问题时(数据库查询时间长)。
所以,我试着编写了一个简单的代码来理解这个问题。
代码很简单,客户端访问路由时,应用休眠10秒才returns出结果。
它看起来像这样:
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 秒睡眠仍在发生时处理下一个请求。
尽管进行了一些谷歌搜索,但关于这个主题并没有真正直接的答案。
如果有人对此有任何意见,请在评论中提出。
非常感谢您的意见。非常感谢大家的宝贵时间。
编辑 关于此的更新,我正在探索更多并发现了流程管理器的概念。例如,我们可以使用进程管理器 (gunicorn) 运行 uvicorn。通过添加更多工人,我能够实现类似 Flask 的功能。然而,仍在测试其极限。 https://www.uvicorn.org/deployment/
感谢所有留下评论的人!欣赏一下。
我认为您在异步框架 FastAPI 中阻塞了一个事件队列,而在 Flask 中请求可能 运行 每个都在新线程中。将所有 CPU 绑定任务移动到单独的进程中,或者在您的 FastAPI 示例中仅在事件循环中休眠(不要在此处使用 time.sleep)。在 FastAPI 中 运行 IO 异步绑定任务
这看起来有点有趣,所以我 运行 用 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 请求的基准测试结果:
FastAPI 与 Uvicorn Workers
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)
对于 Flask:
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 快近 48x,这是非常可以理解的。 ASGI vs WSGI,所以我 运行 有 1 个并发:
FastAPI - UvicornWorkers:测试时间:1.615 秒
FastAPI - 纯 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)
纯独角兽,不过这次是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)
您正在 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)不提供替代品。他们取而代之的是标准库中的猴子补丁函数,使它们成为非阻塞的。据我所知,对于较新的异步框架和库来说情况并非如此,因为它们旨在允许开发人员使用异步等待语法。
为什么代码很慢
阻塞操作将停止您的事件循环运行任务。当您调用 sleep()
函数时,所有任务(请求)都在等待它完成,从而扼杀了异步代码执行的所有好处。
要理解为什么这段代码比较错误,我们应该更好地理解异步代码在Python中是如何工作的,并且对GIL有一些了解。并发和异步代码在 FastAPI 的 docs 中有很好的解释。
@Asotos 已经描述了为什么你的代码很慢,是的,你应该使用协程进行 I/O 操作,因为它们会阻止事件循环执行(sleep()
是一个阻塞操作)。合理地建议使用异步函数,这样事件循环就不会被阻塞,但目前,并不是所有的库都有异步版本。
没有 async
函数和 asyncio.sleep
的优化
如果您不能使用库的异步版本,您可以简单地将路由函数定义为简单的 def
函数,而不是 async def
。
如果路由函数定义为同步(def
),FastAPI会在外部线程池中智能调用该函数,不会阻塞带事件循环的主线程,你的benchmarks会是不使用 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 绑定任务必须 运行 在单独的进程中。