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

Use ØMQ and Tornado for fun and profits

Use ØMQ and Tornado for fun and profits

Des astuces et des bouts de code pour utiliser ØMQ et tornado ensemble.

Boris Feld

June 22, 2013
Tweet

More Decks by Boris Feld

Other Decks in Programming

Transcript

  1. Use ØMQ and Tornado For fun and profits Pytong 2013

    - FELD Boris
  2. /me • FELD Boris - Étudiant • Évangeliste Python et

    Open-Source • Mon quatuor gagnant: • Python/ØMQ/MongoDB/Tornado • @lothiraldan un peu partout sur le web • https://github.com/Lothiraldan/pytong- zeromq-tornado
  3. ØMQ Késako

  4. ØMQ en 2 phrases We took a normal TCP socket,

    injected it with a mix of radioactive isotopes stolen from a secret Soviet atomic research project, bombarded it with 1950-era cosmic rays, and put it into the hands of a drug-addled comic book author with a badly-disguised fetish for bulging muscles clad in spandex. Yes, ØMQ sockets are the world-saving superheroes of the networking world.
  5. Pourquoi ØMQ ? Les + • Plus léger et rapide

    que HTTP. • Communication bi-directionnelle. • Patterns de communication (Pub-Sub, Push- Pull). • Communication n-m.
  6. Pourquoi ØMQ ? Les - • (Beaucoup) plus bas niveau

    que HTTP. • Fonctionne en mode déconnecté.
  7. ØMQ - Que peut-on faire ? Un framework évènementiel distribué.

    BULLSHIT SCORE = 5
  8. Faire le lien entre ØMQ et HTTP. • 2 besoins:

    • Transmettre les requêtes reçues en HTTP vers l’architecture ØMQ. • Transmettre les messages pub/sub en websocket. • Exemple : circus-web.
  9. None
  10. Fuyez gevent !

  11. Use tornado, Luke !

  12. Côté ØMQ

  13. 1) Utilisez ZMQStream import zmq from zmq.eventloop.zmqstream import ZMQStream context

    = zmq.Context() socket = context.socket(zmq.DEALER) def recv_callback(msg): # do stuff with msg pass stream = ZMQStream(socket) stream.on_recv(recv_callback)
  14. 2) Utilisez ZMQ Router import zmq from zmq.eventloop.zmqstream import ZMQStream

    context = zmq.Context() socket = context.socket(zmq.ROUTER) socket.bind('tcp://0.0.0.0:8080') def recv_callback(raw_msg): sid, msg = raw_msg # sid is socket id # do stuff with msg response = 42 socket.send_multipart([sid, response]) stream = ZMQStream(socket) stream.on_recv(recv_callback)
  15. 3) Connecter d’autre sockets import socket from zmq.eventloop import ioloop

    # Create udp socket sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) sock.bind(('0.0.0.0', 5556)) # UDP Socket callback def process_udp_socket(fd, events): data = sock.recv_from(1024) # do stuff with data # Connect udp socket to ioloop ioloop.IOLoop.instance().add_handler(sock.fileno(), process_udp_socket, ioloop.IOLoop.READ)
  16. Côté Tornado

  17. 4) Asynchronous for the win !

  18. L’IOLoop Tornado expliquée

  19. Code synchrone import tornado.ioloop from tornado.web import RequestHandler from models

    import User class MainHandler(RequestHandler): def get(self): user = User.find_one(id='Toto') self.write("Hello %s" % user.name)
  20. Déroulement synchrone Handler Model DB Call

  21. Code asynchrone from tornado.web import RequestHandler, asynchronous from tornado.httpclient import

    AsyncHTTPClient from tornado import gen class AsyncProxyHandler(RequestHandler): @asynchronous @gen.coroutine def get(self): http_client = AsyncHTTPClient() response = yield http_client.fetch("http://example.com") self.finish(response.content)
  22. Déroulement Asynchrone Requête Handler

  23. Asynchrone Handler Requête response = yield http_client.fetch("http://example.com")

  24. Asynchrone Handler Requête

  25. Asynchrone Handler Requête AsynchronousHTTPClient.fetch IOLoop Task (with callback)

  26. Asynchrone Handler Requête AsynchronousHTTPClient.fetch IOLoop Callback with task result

  27. Asynchrone Handler Requête

  28. Comment ça marche ? class Return(Exception): pass class Task(object): def

    __init__(self, task): self.task = task def __call__(self): return self.task() def some_task(): return 42 def generator(): raise Return((yield Task(some_task)))
  29. Analyse gen = generator() print generator # <generator object get

    at 0x110811dc0> # C'est un générateur, consommons-le ! task = next(gen) print task # <__main__.Task object at 0x110a42bd0> # C'est une task, exécutons-la ! result = task() print result # 42 # Renvoyons le résultat au générateur gen.send(result) Return Traceback (most recent call last) <ipython-input-163-b5ca476de253> in <module>() ----> 1 gen.send(result) <ipython-input-162-0f4b6d0ad93b> in generator() 17 18 def generator(): ---> 19 raise Return((yield Task(some_task))) 20 21 Return: 42
  30. Rendre du code asynchrone • Il faut décorer chaque fonction

    avec gen.coroutine. • Et donc faire un yield gen.Task à chaque fonction. • Ça peut commencer à être lourd avec un code compliqué.
  31. Exemple from tornado.web import RequestHandler, asynchronous from tornado import gen

    from .utils import get_client class AsyncProxyHandler(RequestHandler): @asynchronous @gen.coroutine def get(self): self.finish((yield gen.Task(call, 'project', 'list'))) @gen.coroutine def call(service_name, action): client = get_client(service_name) raise gen.Return((yield gen.Task(client.call, action)))
  32. Astuces • On n’arrête pas un generateur avec return, mais

    avec une exception StopIteration. • On peut « retourner » un résultat en levant une exception tornado.gen.Result. • Les exceptions sont automatiquement remontées. • Utilisez python 3.3, return autorisés et yield from vous sauverons la vie.
  33. Template • Les appels asynchrones sont incompatibles avec les templates.

    • Faîtes les appels dans les handlers.
  34. 5) Une requête = une socket

  35. Client import zmq from zmq.eventloop.zmqstream import ZMQStream class ZMQClient(object): def

    __init__(self, endpoint): self.endpoint = endpoint def call(self, message, callback): context = zmq.Context() socket = context.socket(zmq.DEALER) socket.connect(self.endpoint) stream = ZMQStream(socket) stream.on_recv(callback) socket.send(message)
  36. Handler from tornado.web import RequestHandler, asynchronous from tornado import gen

    from client import ZMQClient client = ZMQClient('tcp://127.0.0.1:5555') class GenAsyncHandler(RequestHandler): @asynchronous @gen.coroutine def get(self): response = yield gen.Task(client.call, "ping") self.finish(response)
  37. 6) PubSub - Websocket

  38. Handler Tornado class WebSocketHandler(websocket.WebSocketHandler): clients = {} def open(self, endpoints):

    for endpoint in endpoints: self.clients.setdefault(endpoint, set()).add(self) self.endpoints = endpoints def on_close(self): for endpoint in self.endpoints: self.clients.remove(self)
  39. Handler ØMQ import zmq from zmq.eventloop.zmqstream import ZMQStream class ZeroMQSubConsumer(object):

    def __init__(self): context = zmq.Context() self.sub_socket = context.socket(zmq.SUB) self.sub_socket.setsockopt(zmq.SUBSCRIBE, '') self.stream = ZMQStream(self.sub_socket) self.stream.on_recv(self.process_sub) def connect_to_endpoint(self, endpoint): self.sub_socket.connect(endpoint) def process_sub(self, sub_message): # Sub_message contains [endpoint, message] endpoint, message = sub_message for client in WebSocketHandler.clients[endpoint]: client.write_message(message)
  40. 7) Timeout sur les sockets • Par défaut ØMQ ne

    permet pas de mettre un timeout sur les sockets ØMQ. • Mais grâce à tornado, on va arriver à nos fins.
  41. Client avec timeout import zmq from zmq.eventloop.zmqstream import ZMQStream from

    zmq.eventloop import ioloop class ZMQClient(object): def __init__(self, endpoint): self.endpoint = endpoint def call(self, message, callback): context = zmq.Context() socket = context.socket(zmq.DEALER) socket.connect(self.endpoint) stream = ZMQStream(socket) loop = ioloop.IOLoop.instance() def timeout_callback(): stream.stop_on_recv() stream.close() raise TimeoutError('Call timeout for message', message) timeout = loop.add_timeout(timedelta(seconds=5), timeout_callback) def recv_callback(msg): loop.remove_timeout(timeout) stream.stop_on_recv() stream.close() callback(msg) stream.on_recv(callback) socket.send(message)
  42. Conclusion • Utiliser la puissance de l’ioloop de tornado pour

    un maximum d’efficacité. • Minimiser le code du pont entre zeromq et HTTP. • Il est plus intéressant de faire une API HTTP et de l’appeler depuis un flask/ django/RubyAndRails/JS/Java/Brainfuck/C/ Assembleur...
  43. Questions ?