Александр Кошелев: Препарирование работы...

Post on 16-Jun-2015

849 Views

Category:

Technology

1 Downloads

Preview:

Click to see full reader

DESCRIPTION

Что происходит внутри асинхронного кода? Как быть, когда логика становится cpu-bound? Можно ли сделать гибридную синхронно-асинхронную архитектуру? Я попробую ответить на эти вопросы на примере приложения на Tornado. Сделаю визуализацию работы приложения и предложу пути решения некоторых проблем.

TRANSCRIPT

ПРЕПАРИРОВАНИЕ РАБОТЫАСИНХРОННОГО КОДА

Александр Кошелев, ЯндексPyCon Россия, 2013

ЗАЧЕМAsynchronous is a new coolЭффективностьМного io-boundМало cpu-bound

Высокие нагрузкиМного клиентов

КАКИнструменты на выбор

TwistedTornadoAsyncoreGevent...Node.js

Можно написать своё

EVENT LOOPОпрос сокетовВызов хендлеров

Обработка таймаутовВызов колбеков

EVENT LOOPwhile running: do_handle_sockets()

do_handle_timeouts()

do_handle_callbacks()

ЭТО ЗНАЧИТВсе запросы обрабатываются в одном процессеОбработка одного запроса влияет на другиеТребуется изоляция контекста каждого запроса

ГЛАВНЫЕ ПРАВИЛАНельзя блокироватьсяРазбивка на короткие этапыОбязательно io-bound

ЭКСПЕРИМЕНТTornadocpu-boundОснован на реальной задаче

ЭКСПЕРИМЕНТТЕСТОВОЕ ПРИЛОЖЕНИЕ

Принять запросПараллельно опросить бекэнды — принципиальноОбработать данныеОтдать ответ

ЭКСПЕРИМЕНТТЕСТОВОЕ ПРИЛОЖЕНИЕ

ЭКСПЕРИМЕНТХЕНДЛЕР

class AppHandler(object): @gen.engine def process(self, callback): yield gen.Task(self.do_io) self.do_cpu()

results = yield [gen.Task(self.do_branch), gen.Task(self.do_branch)] self.do_cpu()

yield gen.Task(self.do_io) self.do_cpu()

callback('ok')

ЭКСПЕРИМЕНТХЕНДЛЕР

class AppHandler(object): # ...

def do_cpu(self, cycles=1000000): for _ in xrange(cycles): pass

@gen.engine def do_io(self, callback): yield gen.Task(self.http_client.fetch, 'http://localhost:5001/')

callback(None)

@gen.engine def do_branch(self, callback): yield [gen.Task(self.do_io) for _ in range(5)] self.do_cpu()

callback(None)

ЭКСПЕРИМЕНТПРИЛОЖЕНИЕ

from tornado import web

from handler import AppHandler

class RootHandler(web.RequestHandler): @web.asynchronous def get(self): handler = AppHandler()

handler.process(self.return_response)

def return_response(self, response): self.finish(response)

application = web.Application([ (r'/', RootHandler),])

1 ПАРАЛЛЕЛЬНЫЙ ЗАПРОС

1 ПАРАЛЛЕЛЬНЫЙ ЗАПРОС

N ПАРАЛЛЕЛЬНЫХ ЗАПРОСОВ

3 ПАРАЛЛЕЛЬНЫХ ЗАПРОСА

ВЫВОДЫЗапросы мешают друг другу из-за cpu-bound задачРастет время ответаЖадность

WORKAROUNDFLASK + TORNADO IOLOOP

Синхронный воркерНе жадныйНо нужно несколько процессов

Event loop внутриПараллельный опрос бекэндов

FLASK + TORNADO IOLOOPРЕАКТОР

def do(handler, args=(), kwargs={}): io_loop = tornado.ioloop.IOLoop.instance()

result = [None]

def callback(result_): result[0] = result_

io_loop.stop()

kwargs['callback'] = callback handler(*args, **kwargs)

io_loop.start()

return result[0]

FLASK + TORNADO IOLOOPПРИЛОЖЕНИЕ

from flask import Flask

import handler, reactor

application = Flask(__name__)

@application.route('/')def root(): hndl = handler.AppHandler()

response = reactor.do(hndl.process)

return response

1 ПАРАЛЛЕЛЬНЫЙ ЗАПРОС

1 ПАРАЛЛЕЛЬНЫЙ ЗАПРОС

N ПАРАЛЛЕЛЬНЫХ ЗАПРОСОВ

3 ПАРАЛЛЕЛЬНЫХ ЗАПРОСА

ИТОГИВремя не зависит от числа параллельных запросовБолее предсказуемое поведение

ВЫВОДЫБаланс cpu-bound/io-boundВсё зависит от задачи

ВОПРОСЫ?Спасибо

Александр Кошелев, Яндекс

top related