Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Ecosystème Python pour le web : quelle architec...

Ecosystème Python pour le web : quelle architecture, quel framework pour quelle problématique

Ce talk examine plusieurs solutions pour construire des sites et applications web à l'aide du langage python, en fonction des problématiques à résoudre.

Après un tour d'horizon et une catégorisation macro des différentes librairies et frameworks populaires, nous examinerons les principales fonctions offertes par ceux-ci. Une fois le framework choisi, plusieurs solutions sont envisageables pour traiter les requêtes entrantes. Nous aborderons les différentes mises en oeuvre de la concurrence en fonction du type de performances attendues (I/O, CPU...) avec un accent sur la gestion évènementielle popularisée dans le monde Javascript par node.js.

Le talk se conclura par une revue d'exemples d'architectures utilisables pour des besoins typiques du web d'aujourd'hui : Single Page Application, Real time, IoT, microservices...

Yann Gravrand

March 25, 2016
Tweet

More Decks by Yann Gravrand

Other Decks in Technology

Transcript

  1. OFUOH OFUOH ECOSYSTÈME PYTHON POUR LE WEB Yann Gravrand BreizhCamp

    2016 - Rennes Quelle architecture, quel framework pour quelle problématique
  2. ›  Net-ng - Rennes 20 pers Applications métier en Python

    Forte orientation web Framework web open source à base de composants : www.nagare.org Présentation
  3. ›  Autour du développement web en Python Les grands types

    de librairies et frameworks Différentes façons de traiter les requêtes Des exemples de la vraie vie pour vous inspirer Ce talk
  4. ›  Fournissent des solutions à la plupart des problèmes classiques

    d’une application web : Authentification / sécurité Gestion des URLs Persistance / ORM Génération HTML : moteur de templating, composants… Interaction avec le navigateur : JS, CSS… ›  Exemples Django, Pyramid, Zope, Nagare, CubicWeb… « Full stack » frameworks
  5. ›  Django License BSD depuis 2005 Le plus connu Grosse

    communauté (DSF, DjangoCon…) 18200 ★, 1100 contributeurs Github Projets autour : DjangoCMS… •  Modèle MVC (MTV) •  Langages de templates •  Validation de formulaires •  Persistance via ORM •  Administration avec IHM CRUD automatiques •  … ›  Remarques Peut ressembler à Ruby on Rails (en plus explicite), Play… Beaucoup d’ « applications » (extensions) Pas de support particulier d’AJAX « Full stack » frameworks : exemples
  6. ›  Zope Version 2 : 1998 Version 3 : 2004

    •  ZODB : Base objet •  Langages de templates : DTML, ZPT •  CMS : Plone, CPS jusqu’à 2007 ›  Pyramid 2008 •  Racines Zope •  Modèle « MVC » (RV) •  Langages de templates : Chameleon, Jinja2, … •  Persistance pluggable (ZODB, relationnel…) « Full stack » frameworks : exemples
  7. ›  Nagare 2009 •  Approche composants •  Ecriture des vues

    en Python : pas de langage de templating nécessaire •  Persistance via ORM •  Abstractions de navigation par callbacks •  Sérialisation de l’état complet de l’application •  Ajax : par changement de renderer « Full stack » frameworks : exemples
  8. ›  N’adressent volontairement que des parties essentielles : Gestion des

    URLs API accès aux éléments de la requête / réponse Intégration d’un moteur de templating pour la génération HTML ›  Simples dans l’approche ›  Pour des use cases simples… ›  … Attention à l’évolutivité de l’application « Micro » frameworks
  9. ›  Flask Créé par A. Ronacher 18700 ★, 288 contributeurs

    Github Moteur de templating (Jinja2) Mécanisme d’extensions pour le reste (DB, Auth, Forms…) Facilement RESTful Bonne doc ›  Bottle 3550 ★, 130 contributeurs Github Moteur de templating builtin et support de plusieurs formats ›  Web.py, … Plus très actif… « Micro » frameworks : exemples
  10. « Micro » frameworks : exemples # Encoding: utf-8 from

    flask import Flask, render_template app = Flask(__name__) @app.route('/status') def status(): return 'I\'m Flask, up and running!' @app.route('/', defaults={'name': 'Flask'}) @app.route('/hello/<name>') def index(name): return render_template('hello.html', name=name) if __name__ == '__main__': app.run(host='0.0.0.0', port=5000) <!doctype html> <title>Hello from Flask</title> {% if name %} <h1>Hello {{ name }}!</h1> {% else %} <h1>Hello World!</h1> {% endif %} templates/   hello.html   app.py  
  11. « Micro » frameworks : exemples from bottle import route,

    run, template @route('/hello/<name>') def index(name): return template('<b>Hello {{name}}</b>!', name=name) run(host='localhost', port=8080)
  12. ›  Bloquant i.e : on attend que la requête soit

    traitée dans son intégralité avant d’en accepter une autre Pas trop vite : une requête à la fois Req.  1   Req.  2   Req.  3   Temps  
  13. ›  Grosse capacité en cuisine ›  Un(e) seul(e) hôte(sse) de

    caisse ›  File de clients ›  On ne passe pas au client suivant tant que le premier n’est pas servi ›  L’hôte(sse) bloque… Le fast food : ex 1 Temps   Client  1   Client  2   Client  3                
  14. github.com/ygravrand/events/breizhcamp2016/ex1_one_at_a_time Ex 1 : flask dev # Encoding: utf-8 import

    time from flask import Flask app = Flask(__name__) def kitchen_work(): time.sleep(5) @app.route('/') def fast_food_host(): print 'Order sent to the kitchen, waiting...' kitchen_work() print 'Burger received from kitchen!' return 'Here\'s your order.' if __name__ == '__main__': app.run(host='0.0.0.0', port=5000)
  15. ./fast_food.sh ./clients.sh (ab -n 3 -c 3) 3 req *

    5 sec = 15 sec Ex 1 : flask dev * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit) Order sent to the kitchen, waiting... 192.168.99.1 - - [18/Mar/2016 15:32:28] "GET / HTTP/1.0" 200 - Burger received from kitchen! Order sent to the kitchen, waiting... Burger received from kitchen! Order sent to the kitchen, waiting... 192.168.99.1 - - [18/Mar/2016 15:32:33] "GET / HTTP/1.0" 200 - Burger received from kitchen! 192.168.99.1 - - [18/Mar/2016 15:32:38] "GET / HTTP/1.0" 200 - Concurrency Level: 3 Time taken for tests: 15.036 seconds
  16. ›  Réflexe possible : utiliser les threads (Java qqu’un ?)

    Traiter plusieurs requêtes en // Req.  1   Req.  2   Req.  3   Temps   Thread  1   Thread  2   Thread  3  
  17. ›  Threads : plus légers que les processus ›  Threads

    Python : Ce sont bien des threads OS (scheduling en interaction avec l’OS) Mais 1 seul thread permis à un instant t par l’interpréteur Python (pas de concurrence) … détails ci-après Multi threading
  18. ›  1) You do not talk about the GIL › 

    2) You DO NOT talk about the GIL ›  3) One fight at a time (on the processor) The rules of Python.
  19. ›  Un nom : GIL (Global Interpreter Lock) voir https://wiki.python.org/moin/GlobalInterpreterLock

    ›  GVL en Ruby ›  Pas dans toutes les implémentations Python Cpython, PyPy Pas Jython, IronPython ›  Pourquoi ? Pour protéger les structures de données internes qui ne sont pas thread safe, pour simplifier les problèmes de concurrence, les interactions avec les extensions C, etc Multi threading / GIL
  20. ›  Parallélisme Pas d’exécution parallèle des threads Sur plusieurs cœurs,

    exécution des threads sérialisée Et ce, même si l’OS « a envie » d’exécuter plusieurs threads simultanément Multi threading Temps   Thread  1   Thread  2   Thread  3  
  21. ›  CPU Bound Temps d’exécution limité par le CPU Application

    effectuant majoritairement des calculs Ex : traitement d’images en mémoire Dans notre fast food : la plonge, peler des patates ›  I/O Bound Temps d’exécution limité par les E/S Application effectuant beaucoup d’E/S Ex : interfaces avec bases de données, systèmes de fichiers, autres systèmes externes Dans notre fast food : les hôte(sse)s Définitions
  22. ›  CPU Bound ! Multi threading pas recommandé en Python

    ! Multi process ? Cf. plus tard ›  I/O Bound !  Multi threading : GIL pas forcément trop impactant Modèle plutôt léger !  D’autres alternatives si énormément de requêtes / s cf. plus tard Implications
  23. ›  Un processus parent crée plusieurs processus enfants et les

    coordonne ›  Par ex. avec le module multiprocessing ›  Pool de processus Multi processing
  24. ›  Effet minimal du GIL : X GIL ›  Utilise

    tous les CPUs / cœurs ›  « Safe » : pas de corruption liée à un partage de mémoire ›  Encourage de bonnes pratiques pour la scalabilité (stateless, passage de messages) différents processus ≈ différentes machines ›  Passage de messages entre processus : coûteux Multi processing : pour la concurrence
  25. ›  Un processus gère une requête ›  Processus : lourds

    à créer, limités en nb ›  Pool de processus en pre-fork ›  Ex : Gunicorn mode synchrone Multi processing : pour le web Req.  1   Req.  2   Req.  3   Temps   Process  1   Process  2   Process  3   Req.  4  
  26. ›  Grosse capacité en cuisine ›  Plusieurs hôte(sse)s de caisses

    ›  Chaque hôte(sse) gère chaque client séquentiellement, et passe du temps à attendre Le fast food : ex 2 Temps   Hôte(sse)  1   Hôte(sse)  2   Hôte(sse)  3   Client  1   Client  2   Client  3                 File  de  clients  
  27. breizhcamp2016/ex2_multi_process ./fast_food.sh ./clients.sh (ab -n 3 -c 3) Ex 2

    : flask gunicorn [2016-03-18 15:43:22 +0000] [1] [INFO] Starting gunicorn 19.4.5 [2016-03-18 15:43:22 +0000] [1] [INFO] Listening at: http:// 0.0.0.0:8000 (1) [2016-03-18 15:43:22 +0000] [1] [INFO] Using worker: sync [2016-03-18 15:43:22 +0000] [11] [INFO] Booting worker with pid: 11 [2016-03-18 15:43:23 +0000] [12] [INFO] Booting worker with pid: 12 [2016-03-18 15:43:23 +0000] [13] [INFO] Booting worker with pid: 13 [2016-03-18 15:43:23 +0000] [14] [INFO] Booting worker with pid: 14 Order sent to the kitchen, waiting... Order sent to the kitchen, waiting... Order sent to the kitchen, waiting... Burger received from kitchen! Burger received from kitchen! Burger received from kitchen! Concurrency Level: 3 Time taken for tests: 5.021 seconds
  28. ›  Si bon dimensionnement / cuisine, optimise le temps en

    cuisine et le temps d’attente des clients ›  Traitements CPU « raisonnables » possibles (encaissement, …) ›  Attention à ne pas peler des patates ! Pas désastreux mais ça bloque une file Ex 2 : conclusions
  29. ./sports_fan_bus.sh (ab -n 50 -c 50) 50 supporters rentrent en

    même temps 50 req / 4 workers * 5 sec = 62.5 sec Ex 2 : limité au nombre d’hôte(sse)s… Concurrency Level: 50 Time taken for tests: 65.260 seconds
  30. ›  Si j’ai à gérer plusieurs dizaines de milliers de

    clients ? ›  Et que ma cuisine a cette capacité ? http://www.kegel.com/c10k.html 1Ok problem
  31. ›  Autres termes : asynchrone, non bloquant, … ›  Un

    élément central (Reactor / Event loop) traite une file d’évènements ›  Dès qu’un traitement nécessite des I/O, on passe à autre chose ›  Vous êtes vous-même une boucle événementielle Evénementiel : principes
  32. ›  Capacité énorme en cuisine ›  Un(e) seul(e) hôte(sse) de

    caisse ›  Passe au client suivant dès qu’une ligne (burger, frites…) nécessite de l’attente ›  Les lignes préviennent l’hôte(sse)… ›  Qui n’est pas bloquée(e) Le fast food : ex 3
  33. Le fast food : ex 3 Temps   Cde  1

      finie   Cde  1   Cde  2   finie   Cde  2   Hôte(sse)  
  34. Ex 3 : callbacks avec twisted breizhcamp2016/ex3_callbacks # Encoding: utf-8

    from twisted.web import ... def kitchen_work(get_result): return deferLater(reactor, 5, get_result) class FastFoodResource(resource.Resource): def on_burger_ready(self, request): print 'Burger received from kitchen!’ request.setHeader("content-type", "text/plain") request.write('Here\'s your order.') request.finish() def render_GET(self, request): print 'Order sent to the kitchen, waiting...' deferred = kitchen_work(lambda: request) deferred.addCallback(self.on_burger_ready) return server.NOT_DONE_YET if __name__ == "__main__": reactor.listenTCP(5000, server.Site(FastFoodResource())) reactor.run()
  35. ./sports_fan_bus.sh (ab -n 50 -c 50) 50 req * 5

    sec = 5 sec Ex 3 : twisted Concurrency Level: 50 Time taken for tests: 5.120 seconds Order sent to the kitchen, waiting... Order sent to the kitchen, waiting... Order sent to the kitchen, waiting... Order sent to the kitchen, waiting... Order sent to the kitchen, waiting... Order sent to the kitchen, waiting... Order sent to the kitchen, waiting... [...] Burger received from kitchen! Burger received from kitchen! Burger received from kitchen! Burger received from kitchen!
  36. breizhcamp2016/ex3_aiohttp Ex 3 : aiohttp / asyncio, PY3.4 # Encoding:

    utf-8 import asyncio from aiohttp import web @asyncio.coroutine def kitchen_work(): yield from asyncio.sleep(5) @asyncio.coroutine def fast_food_host(request): print('Order sent to the kitchen, waiting...', flush=True) yield from kitchen_work() print('Burger received from kitchen!', flush=True) return web.Response(body='Here\'s your order.'.encode('utf-8')) app = web.Application() app.router.add_route('GET', '/', fast_food_host) if __name__ == '__main__': web.run_app(app, host='0.0.0.0', port=8000)
  37. ./sports_fan_bus.sh (ab -n 50 -c 50) 50 req * 5

    sec = 5 sec Ex 3 : aiohttp / asyncio, PY3.4 Concurrency Level: 50 Time taken for tests: 5.267 seconds Order sent to the kitchen, waiting... Order sent to the kitchen, waiting... Order sent to the kitchen, waiting... Order sent to the kitchen, waiting... Order sent to the kitchen, waiting... Order sent to the kitchen, waiting... Order sent to the kitchen, waiting... [...] Burger received from kitchen! Burger received from kitchen! Burger received from kitchen! Burger received from kitchen!
  38. ›  Nécessite d’écrire toute l’application en utilisant ce style :

    Callbacks yield / yield from / async await Style « non bloquant » ›  Autre façon de penser la concurrence Plus nécessairement besoin de mutex, lock, … Autres paradigmes Ex 3 : conclusions / implémentation
  39. ›  Intéressant si travail d’hôte(sse) est surtout de l’orchestration › 

    La limite est en cuisine : I/O Bound ›  CPU réduit au minimum : encaissement rapide ;-) Ex 3 : conclusions
  40. ›  J’écris mon application comme dans l’exemple 1 ! › 

    C’est « magique » ?... Ex 4 : Gunicorn + Flask + gevent
  41. breizhcamp2016/ex4_gevent Ex 4 : Gunicorn + Flask + gevent #

    Encoding: utf-8 import time from flask import Flask app = Flask(__name__) def kitchen_work(): time.sleep(5) @app.route('/') def fast_food_host(): print 'Order sent to the kitchen, waiting...' kitchen_work() print 'Burger received from kitchen!' return 'Here\'s your order.' if __name__ == '__main__': app.run(host='0.0.0.0', port=5000) Concurrency Level: 50 Time taken for tests: 5.266 seconds
  42. ›  Fonctions monkey-patchées pour utiliser leurs versions non bloquantes :

    time.sleep ! gevent.sleep sockets... ›  Gunicorn appelle monkey.patch_all de gevent Ex 4 : Gunicorn + Flask + gevent
  43. ›  Simplicité (en surface...) ›  Pas pur python, nécessite C

    & ASM ›  Sur 1 process : toujours pas d’utilisation des CPU/cœurs multiples Ex 4 : conclusions
  44. ›  Norme pour connecter votre serveur web frontal à votre

    application web WSGI : Web Server Gateway Interface Serveur  web   Connecteur  /   serveur  WSGI   Votre   applicaTon   AJP   FastCGI   …   API   programmaTque   X  process   X  process  
  45. ›  Norme pour connecter votre serveur web frontal à votre

    application web WSGI : Web Server Gateway Interface Apache   Nginx…   Flup   Gunicorn   uWSGI…   Django   Flask   Pyramid…   AJP   FastCGI   …   API   programmaTque   X  process   X  process  
  46. ›  API : appel de fonction ›  Interface synchrone –

    bloquante ›  Multi thread, multi process : OK ›  Serveurs événementiels à base de callback, yield (ex 3) : KO ›  Green threads (ex 4) : OK WSGI : Web Server Gateway Interface def application(env, start_response): start_response('200 OK', [('Content-Type','text/html')]) return [b"Hello World"]
  47. ›  Framework Full Stack Intranet / CMS Votre   applicaTon

      DB   Cache   Files   d’a<ente   Client  web   Full  Stack  Framework   MulT  process  /   mulT  thread  
  48. ›  Javascript + API REST SPA (Single Page Application) Appli

      Javascript   Appli   Python   API   REST   Client  web   Micro  Framework   MulT  process  /   mulT  thread  
  49. Micro services (attention buzzword) Micro  service  mail   API  

    REST   Micro  Framework   MulT  process  /  mulT  thread   Appli   principale   File   d’a<ente   Micro  service  persistance   API   REST   Micro  Framework   MulT  process  /  mulT  thread   DB  
  50. ›  Bcp de petites connexions très fréquentes IoT (Internet of

    Things) API   REST   Micro  Framework   Appli  Python   EvénemenTel   Traitements   File   Pool  de  process   Traitements  
  51. ›  Connexions à maintenir ouvertes Real time Appli   Javascript

      Client  web   Websockets   /home…   Bus   Appli  Python  principale   Full  Stack  Framework   MulT  process  /  mulT  thread   Appli  RealTme   EvénemenTel   Tornado,  Autobahn,  …  
  52. ›  Gevent, multi process ›  Psycogreen / Psycopg2 ›  2500

    req/s (pas juste du 200 OK) sur 1 machine (OK bien puissante) ›  Python c’est lent ;-) Et notre anecdote…
  53. ›  Docs. Autobahn : bonnes explications http://autobahn.ws/python/asynchronous- programming.html ›  Docs

    Tornado, Twisted, Gevent ›  GIL Dave Beazley - Understanding the Python GIL : https://www.youtube.com/watch?v=Obt-vMVdM8s Ressources