Slide 1

Slide 1 text

Virtual Lines with asyncIO Jose Ignacio Galarza @igalarzab Miguel Araujo @maraujop

Slide 2

Slide 2 text

@ticketeaeng

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

virtual line

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

ticketea Why Python? ✤ Because we are not in the RubyCON :D ✤ Python 3 fixes some issues with concurrency ✤ This is a typical problem to be solved with async I/O ✤ Python scales without problems ;)

Slide 9

Slide 9 text

ticketea Why asyncIO? ✤ This problem is I/O bound ✤ It’s bundled in the standard library ✤ It has a powerful community behind it ✤ One of the most readable concurrent paradigms in Python

Slide 10

Slide 10 text

ticketea Why websockets? ✤ Two-ways communication between user and server ✤ We need almost real-time communication ✤ There is no-overhead in the payloads ✤ It’s possible to update from HTTP to Websocket ✤ Easy to pass through firewalls

Slide 11

Slide 11 text

ticketea Why Redis? ✤ We need a cache to store counters atomically ✤ We don’t need a huge transactional engine ✤ It’s pretty fast ✤ It has a lot of libraries (also for asyncIO)

Slide 12

Slide 12 text

how it works

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

VLMn RESOURCE prepare_purchase with token signed access_resource pay leaves_resource CLIENTS_IN_RESOURCE++ CLIENTS_IN_RESOURCE - 1

Slide 16

Slide 16 text

show me the code

Slide 17

Slide 17 text

ticketea New keywords async def name: await coro @asyncio.coroutine def name: yield from coro Python 3.4 Python 3.5

Slide 18

Slide 18 text

ticketea HTTPServer class HTTPServer: def __init__(self, loop, host='127.0.0.1', port=8080) async def start(self) async def _start_http_server(self) async def _upgrade_to_websocket(self, request) async def _access_resource(self, request) async def _leave_resource(self, request) async def _config_resource(self, request)

Slide 19

Slide 19 text

ticketea Where everything almost begins class HTTPServer: async def start(self): await self.redis.start() await self._start_http_server() async def _start_http_server(self): return await self.loop.create_server( self.app.make_handler(), self.host, self.port )

Slide 20

Slide 20 text

ticketea Where everything begins if __name__ == '__main__': loop = asyncio.get_event_loop() server = HTTPServer(loop, host, port) loop.run_until_complete(server.start()) print('Listening to {0}:{1}'.format(host, port)) try: loop.run_forever() except KeyboardInterrupt: loop.call_soon(loop.stop) print('Stopping server...')

Slide 21

Slide 21 text

ticketea HTTPServer: From HTTP to Websocket async def _upgrade_to_websocket(self, request): websocket = web.WebSocketResponse() await websocket.prepare(request) resource_id = request.match_info.get('resource_id') vlm = await self._get_vlm(resource_id) ws_handler = WebsocketHandler(self.loop, websocket, vlm) await ws_handler.process_connection()

Slide 22

Slide 22 text

ticketea VirtualLineManager class VirtualLineManager(object): def __init__(self, loop, redis, resource_id) async def init_resource(self) def start_updating_clients(self) async def keep_clients_updated(self) async def let_clients_pass(self) def add_connection(self, turn, handler) async def take_turn(self)

Slide 23

Slide 23 text

ticketea VirtualLineManager async def init_resource(self): await self.redis.init_turn_granted_access(self.rid) await self.redis.init_clients_in_resource(self.rid) await self.redis.init_max_clients_in_resource(self.rid) def start_updating_clients(self): self.loop.create_task(self.keep_clients_updated())

Slide 24

Slide 24 text

ticketea VirtualLineManager async def keep_clients_updated(self): await self.let_clients_pass() turn = await self.redis.get_turn_granted_access(self.resource_id) message = json.dumps({"granted_access": "true", "sig": ""}) while self.conn_handlers: if int(self.conn_handlers[0]['turn']) <= int(turn): conn = self.conn_handlers.pop() conn['handler'].send_message(message) else: break message = json.dumps({'last_turn': turn}) connections = deque() for conn_handler in self.conn_handlers: conn_handler['handler'].send_message(message) connections.append(conn_handler) self.conn_handlers = connections self.loop.call_later( self.UPDATE_INTERVAL, lambda: self.start_updating_clients())

Slide 25

Slide 25 text

ticketea RedisHandler class RedisHandler: def __init__(self, host='127.0.0.1', port=6379, poolsize=10) async def start(self) async def access_resource(self, rid) async def leave_resource(self, rid) async def get_last_given_turn(self, rid) async def get_clients_in_resource(self, rid) async def init_clients_in_resource(self, rid) async def set_clients_in_resource(self, rid, clients) async def get_max_clients_in_resource(self, rid) async def init_max_clients_in_resource(self, rid) async def set_max_clients_in_resource(self, rid, max_clients) async def get_turn_granted_access(self, rid) async def init_turn_granted_access(self, rid) async def inc_turn_granted_access(self, rid, incr_quantity) async def take_turn(self, rid): return await self.redis.hincrby(rid, self.LAST_GIVEN_TURN, 1)

Slide 26

Slide 26 text

CPU bound processes

Slide 27

Slide 27 text

http://bit.ly/1MCDoKH

Slide 28

Slide 28 text

No content

Slide 29

Slide 29 text

DEMO TIME

Slide 30

Slide 30 text

https://www.youtube.com/watch?v=7g0iBWKAvBQ

Slide 31

Slide 31 text

ticketea questions?

Slide 32

Slide 32 text

thanks! Jose Ignacio Galarza @igalarzab Miguel Araujo @maraujop