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.
3 Réponses :
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
Salut Hubert, merci beaucoup pour la réponse! Cela expliquerait pourquoi les choses agissent comme elles le sont! Appréciez le commentaire!
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:
gunicorn -w 4 -k uvicorn.workers.UvicornWorker fast_api:appuvicorn fast_api:app --reloadVoici 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)
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
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, "/")
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?
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.
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!