繁体   English   中英

架构 Flask 与 FastAPI

[英]Architecture Flask vs FastAPI

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

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

快速API

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)

Flask

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 客户端同时访问它们。 以下是结果:

快速API

在此处输入图像描述

Flask

在此处输入图像描述

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

尽管做了一些谷歌搜索,但这个话题并没有真正的直接答案。
如果有人有任何评论可以阐明这一点,请将它们放在评论中。

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

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

感谢所有留下评论的人。 欣赏它。

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

Flask

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, "/")

快速API

from fastapi import FastAPI


app = FastAPI(debug=False)


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

我对 FastAPI 进行了 2 次测试,结果有很大不同:

  1. gunicorn -w 4 -k uvicorn.workers.UvicornWorker fast_api:app
  2. 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)

对于 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 快了近48 倍,这是非常可以理解的。 ASGI vs WSGI ,所以我跑了1个并发:

FastAPI - UvicornWorkers测试时间:1.615 秒

FastAPI - Pure Uvicorn测试时间:2.681 秒

Flask测试时间:5.541 秒

我进行了更多测试以使用生产服务器测试 Flask。

5000 请求 1000 并发

Flask 带女服务员

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)

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

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 语法。

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

为什么代码很慢

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

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

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

没有async函数和asyncio.sleep优化

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

如果路由 function 被定义为同步( def ),FastAPI 会在外部线程池中智能地调用这个 function,并且不会阻塞带有事件循环的主线程,并且您的基准测试会更好,而不使用await asyncio.sleep() . 节详细解释。

解决方案

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 密集型任务必须在单独的进程中运行。

在 FastAPI 中实际需要做的实际上是在实际场景中使用后台任务,您可能会发送 email 或对数据库进行大量查询等

快速API

from fastapi import FastAPI, BackgroundTasks
import time
app = FastAPI()

def sleep(msg):
    time.sleep(10)
    print(msg)

@app.get('/')
async def root(background_tasks: BackgroundTasks):
    msg= 'Sleeping for 10'
    background_tasks.add_task(sleep, msg)
    print('Awake')
    return {'message': 'hello'}

然后尝试检查基准

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM