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:app
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)
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!