11
votes

Flacon d'architecture vs FastAPI

J'ai bricolé Flask et FastAPI pour voir comment il agit en tant que serveur.
L'une des principales choses que j'aimerais savoir est comment Flask et FastAPI traitent les demandes multiples de plusieurs clients.
Surtout lorsque le code a des problèmes d'efficacité (longue durée de requête de la base de données).

J'ai donc essayé de créer un code simple pour comprendre ce problème.
Le code est simple, lorsqu'un client accède à la route, l'application se met en veille pendant 10 secondes avant de renvoyer un résultat.
Cela ressemble à quelque chose comme ceci:

FastAPI

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()

Ballon

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)

Une fois les applications ouvertes, j'ai essayé d'y accéder en même temps via 2 clients Chrome différents. Voici les résultats:

FastAPI

entrez la description de l'image ici

Ballon

entrez la description de l'image ici

Comme vous pouvez le voir, pour FastAPI, le code attend d'abord 10 secondes avant de traiter la requête suivante. Alors que pour Flask, le code traite la demande suivante pendant que le sommeil de 10 secondes se produit toujours.

Malgré un peu de recherche sur Google, il n'y a pas vraiment de réponse claire à ce sujet.
Si quelqu'un a des commentaires qui peuvent éclairer ce sujet, veuillez les laisser dans les commentaires.

Vos opinions sont toutes appréciées. Merci beaucoup pour votre temps.

EDIT Une mise à jour à ce sujet, j'explore un peu plus et trouve ce concept de gestionnaire de processus. Par exemple, nous pouvons exécuter uvicorn en utilisant un gestionnaire de processus (gunicorn). En ajoutant plus de travailleurs, je peux réaliser quelque chose comme Flask. Toujours tester les limites de cela cependant. https://www.uvicorn.org/deployment/

Merci à tous ceux qui ont laissé des commentaires! Appréciez-le.


2 commentaires

La partie la plus importante concernant les performances et la concurrence sinon le framework utilisé mais le serveur WSGI et ses paramètres. (Le serveur de développement intégré n'est pas adapté à la production.) Lors de tests approfondis, j'ai remarqué qu'il pouvait faire la différence entre "échoue sous charge" et "centaines de requêtes par seconde".


Salut Klaus, Merci beaucoup pour le commentaire. Cela a dissipé certains malentendus de ma part sur les sections sur lesquelles je devrais me concentrer. Appréciez-le!


3 Réponses :


2
votes

Je pense que vous bloquez une file d'attente d'événements dans FastAPI qui est un cadre asynchrone alors que dans Flask, les demandes sont probablement exécutées chacune dans un nouveau thread. Déplacez toutes les tâches liées au processeur vers des processus séparés ou, dans votre exemple FastAPI, veillez simplement à la boucle d'événements (n'utilisez pas time.sleep ici). Dans FastAPI, exécutez les tâches liées aux E / S de manière asynchrone


1 commentaires

Salut Hubert, merci beaucoup pour la réponse! Cela expliquerait pourquoi les choses agissent comme elles le sont! Appréciez le commentaire!



15
votes

Cela m'a semblé un peu intéressant, j'ai donc effectué quelques tests avec ApacheBench :

Ballon

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)

FastAPI

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)

J'ai effectué 2 tests pour FastAPI, il y avait une énorme différence:

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

Voici donc les résultats de l'analyse comparative pour 5000 requêtes avec une simultanéité de 500:

FastAPI avec les travailleurs Uvicorn

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)

FastAPI - Uvicorn pur

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)

Pour flacon :

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)

Résultats totaux

Flacon : Durée des tests: 27,827 secondes

FastAPI - Uvicorn : Durée des tests: 1,562 secondes

FastAPI - Uvicorn Workers : Durée des tests: 0,577 seconde


Avec Uvicorn Workers, FastAPI est presque 48 fois plus rapide que Flask, ce qui est très compréhensible. ASGI vs WSGI , donc j'ai couru avec 1 concurreny:

FastAPI - UvicornWorkers : Durée des tests: 1,615 secondes

FastAPI - Pure Uvicorn : Durée des tests: 2,681 secondes

Flacon : Durée des tests: 5,541 secondes

J'ai effectué plus de tests pour tester Flask avec un serveur de production.

5000 Requête 1000 Concurrence

Flacon avec serveuse

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)

Gunicorn avec Uvicorn Workers

from fastapi import FastAPI


app = FastAPI(debug=False)


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

Pure Uvicorn, mais cette fois 4 travailleurs uvicorn fastapi:app --workers 4

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


7 commentaires

Salut Yagizcan! Merci beaucoup pour l'analyse approfondie! Les résultats de référence sont très informatifs sur les différences entre eux. Appréciez-le!


Comment avez-vous exécuté l'application Flask? Il est étrange que chaque requête prenne 2,7 secondes pour un exemple aussi simple ...


@marianobianchi j'ai couru avec app.run() , avec une concurrence élevée ce n'est pas si étrange que je pense.


C'est ce que je pensais. Vous comparez un serveur prêt pour la production comme uvicorn avec un serveur de développement comme Werkzeug. Vous devriez comparer avec flask en cours d'exécution avec serveuse, qui est la méthode recommandée pour déployer une application flask prête pour la production: flask.palletsprojects.com/en/1.1.x/tutorial/deploy / ...


@marianobianchi super, en fait, j'ai comparé le code d'OP, mais je vais à nouveau exécuter les tests avec la serveuse et mettre à jour la question avec de nouveaux résultats, merci!


analyse précise, nous sommes presque compromis de migrer vers FastAPI pour les nouvelles versions de nos produits. Je suis tombé sur encore plus de retards en mode débogage, en particulier lorsque j'utilisais derrière un proxy inverse.


Et oui. Il existe un WSGIMIDDLEWARE pour fastapi. Il active l'option fastapi wsgi. Pourriez-vous essayer cela avec gunicorn avec des travailleurs meinheld?



2
votes

Vous utilisez la fonction time.sleep() , dans un point de terminaison async . time.sleep() bloque et ne doit jamais être utilisé dans du code asynchrone. Ce que vous devriez utiliser est probablement la fonction 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)

De cette façon, chaque demande prendra environ 10 secondes pour se terminer, mais vous serez en mesure de traiter plusieurs demandes simultanément.

En général, les frameworks async offrent des remplacements pour toutes les fonctions de blocage à l'intérieur de la bibliothèque standard (fonctions de veille, fonctions d'E / S, etc.). Vous êtes censé utiliser ces remplacements lors de l'écriture de code asynchrone et (facultativement) les await .

Certains frameworks et bibliothèques non bloquants tels que gevent n'offrent pas de remplacements. Au lieu de cela, ils ont des fonctions de patch monkey dans la bibliothèque standard pour les rendre non bloquants. Ce n'est pas le cas, pour autant que je sache, pour les nouveaux frameworks et bibliothèques async, car ils sont destinés à permettre au développeur d'utiliser la syntaxe async-await.


0 commentaires