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. /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
  2. Ø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.
  3. Pourquoi ØMQ ? Les + • Plus léger et rapide

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

    que HTTP. • Fonctionne en mode déconnecté.
  5. 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.
  6. 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)
  7. 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)
  8. 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)
  9. 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)
  10. 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)
  11. 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)))
  12. 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
  13. 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é.
  14. 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)))
  15. 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.
  16. 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)
  17. 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)
  18. 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)
  19. 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)
  20. 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.
  21. 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)
  22. 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...