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

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

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

    View Slide

  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

    View Slide

  3. @ygravrand  

    View Slide

  4. @ygravrand  

    View Slide

  5. ›  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

    View Slide

  6. ›  POC
    ›  Prod
    ›  100k connexions en qq jours …??
    Une anecdote

    View Slide

  7. « FULL STACK » FRAMEWORKS
    « MICRO » FRAMEWORKS
    TOUR D’HORIZON

    View Slide

  8. ›  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

    View Slide

  9. ›  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

    View Slide

  10. ›  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

    View Slide

  11. ›  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

    View Slide

  12. ›  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

    View Slide

  13. ›  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

    View Slide

  14. « 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/')
    def index(name):
    return render_template('hello.html', name=name)
    if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

    Hello from Flask
    {% if name %}
    Hello {{ name }}!
    {% else %}
    Hello World!
    {% endif %}
    templates/  
    hello.html  
    app.py  

    View Slide

  15. « Micro » frameworks : exemples
    from bottle import route, run, template
    @route('/hello/')
    def index(name):
    return template('Hello {{name}}!', name=name)
    run(host='localhost', port=8080)

    View Slide

  16. OULÀ, PAS TROP VITE
    MULTI THREADING
    MULTI PROCESSING
    EVÉNEMENTIEL
    TRAITER LES REQUÊTES

    View Slide

  17. ›  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  

    View Slide

  18. ›  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                

    View Slide

  19. h

    View Slide

  20. 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)

    View Slide

  21. ./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

    View Slide

  22. ›  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  

    View Slide

  23. ›  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

    View Slide

  24. ›  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.

    View Slide

  25. ›  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

    View Slide

  26. ›  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  

    View Slide

  27. ›  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

    View Slide

  28. ›  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

    View Slide

  29. ›  Un processus parent crée plusieurs
    processus enfants et les coordonne
    ›  Par ex. avec le module multiprocessing
    ›  Pool de processus
    Multi processing

    View Slide

  30. ›  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

    View Slide

  31. ›  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  

    View Slide

  32. ›  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  

    View Slide

  33. Hôte(sse)  1  
    Hôte(sse)  2  
    Hôte(sse)  3  
    File  de  clients  

    View Slide

  34. 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

    View Slide

  35. ›  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

    View Slide

  36. ./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

    View Slide

  37. ›  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

    View Slide

  38. ›  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

    View Slide

  39. ›  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

    View Slide

  40. Le fast food : ex 3
    Temps  
    Cde  1  
    finie  
    Cde  1  
    Cde  2  
    finie  
    Cde  2  
    Hôte(sse)  

    View Slide

  41. ›  Explicitement
    Callbacks (mécanisme de rappel)
    Coroutines explicites
    ›  Implicitement
    Green threads
    Comment rendre la main ?

    View Slide

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

    View Slide

  43. ./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!

    View Slide

  44. 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)

    View Slide

  45. ./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!

    View Slide

  46. breizhcamp2016/ex3_tornado
    breizhcamp2016/ex3_asyncio_py35
    Ex 3 : autres implémentations

    View Slide

  47. ›  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

    View Slide

  48. ›  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

    View Slide

  49. ›  Green threads coopératifs
    ›  ≠ threads OS
    Ex 4 : gevent

    View Slide

  50. ›  J’écris mon application comme dans
    l’exemple 1 !
    ›  C’est « magique » ?...
    Ex 4 : Gunicorn + Flask + gevent

    View Slide

  51. 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

    View Slide

  52. ›  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

    View Slide

  53. ›  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

    View Slide

  54. ›  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  

    View Slide

  55. ›  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  

    View Slide

  56. ›  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"]

    View Slide

  57. DES PROPOSITIONS D’ARCHITECTURE,
    DE TECHNOS, FRAMEWORKS
    CAS D’USAGE

    View Slide

  58. ›  Framework Full Stack
    Intranet / CMS
    Votre  
    applicaTon  
    DB  
    Cache  
    Files  
    d’aClient  web  
    Full  Stack  Framework  
    MulT  process  /  
    mulT  thread  

    View Slide

  59. ›  Javascript + API REST
    SPA (Single Page Application)
    Appli  
    Javascript  
    Appli  
    Python  
    API  
    REST  
    Client  web   Micro  Framework  
    MulT  process  /  
    mulT  thread  

    View Slide

  60. Micro services (attention buzzword)
    Micro  service  mail  
    API  
    REST  
    Micro  Framework  
    MulT  process  /  mulT  thread  
    Appli  
    principale  
    File  
    d’aMicro  service  persistance  
    API  
    REST  
    Micro  Framework  
    MulT  process  /  mulT  thread  
    DB  

    View Slide

  61. ›  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  

    View Slide

  62. ›  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,  …  

    View Slide

  63. ›  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…

    View Slide

  64. ›  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

    View Slide

  65. ›  Questions ?
    @ygravrand
    github.com/ygravrand/events
    Merci de votre attention
    Gif  credits:  gify  

    View Slide