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.

410e3353165c33043ab69be7fc366428?s=128

Boris Feld

June 22, 2013
Tweet

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 ?