Андрей Светлов

41
АСИНХРОННЫЕ СЕТЕВЫЕ ПРИЛОЖЕНИЯ НА PYTHON ВЧЕРА, СЕГОДНЯ, ЗАВТРА АНДРЕЙ СВЕТЛОВ [email protected] @andrew_svetlov

Upload: codefest

Post on 09-Jan-2017

845 views

Category:

Software


0 download

TRANSCRIPT

АСИНХРОННЫЕ СЕТЕВЫЕ ПРИЛОЖЕНИЯ НА PYTHON

ВЧЕРА, СЕГОДНЯ, ЗАВТРА

АНДРЕЙ СВЕТЛОВ

[email protected] @andrew_svetlov

О СЕБЕ

Использую python 15+ летPython Core Developer с 2012Соавтор asyncioГлавный разработчик aiohttpДюжина библиотек в сообществе aio­libs

ОПРОСКто использует Python 3?Разрабатывает WEB сайты на Django/FlaskПонимает что такое асинхронное сетевое программированиеИспользует асинхронные frameworks (twisted/tornado/gevent)?

ЗАЧЕМ?

1,000 потоков OS1,000,000 легковесных задач

Hynek Schlawack

asyncio is another interesting topic. I was surprised howalive and well that little corner of the Python community is(albeit in mainly Russian speaking countries).

AIOHTTP ­­ АСИНХРОННЫЙ WEB

КлиентСерверПерманентные соединенияВеб­сокеты

3 ГОДА ИСТОРИИ

Выделилися из asyncio (tulip)21 releases2800+ commits100+ contributors

КЛИЕНТ

REQUESTS

import requests

r = requests.get('https://api.github.com/user', auth=('user', 'pass')) print(r.status_code) print(r.headers['content-type']) print(r.text)

AIOHTTP

import aiohttp

async def coro(): r = await aiohttp.get('https://api.github.com/user', auth=aiohttp.BasicAuth('user', 'pass')) print(r.status) print(r.headers['content-type']) print(await r.text()) r.close()

ПРАВИЛА ДЛЯ ASYNCIO

1. Корутина ­­ это async def2. Вызывай корутину через await3. Если функция содержит await ­­ сделай её корутиной

async def func(): await asyncio.sleep(1)

REQUESTS С СЕССИЕЙ

session = requests.Session()

r = session.get(url)

AIOHTTP С СЕССИЕЙ

session = aiohttp.ClientSession()

async def coro(session): async with session.get(url) as r: print(r.status) print(await r.text())

ТАЙМАУТЫ

async def coro(session): with aiohttp.Timeout(1.5): async with session.get(url) as r: ...

ВЕБ­СОКЕТЫ

async with client.ws_connect( 'http://websocket-server.org/endpoint') as ws:

async for msg in ws: if msg.data == 'close': await ws.close() break else: ws.send_str("Answer on " + msg.data)

СЕРВЕР

DJANGO

from django.conf.urls import url from django.http import HttpResponse

def index(request): return HttpResponse("Hello, world")

urlpatterns = [ url(r'̂$', index), ]

AIOHTTP

from aiohttp import web

async def handler(request): return web.Response(text="Hello, world")

app = web.Application(loop=loop) app.router.add_route('GET', '/', handler) web.run_app(app)

TORNADO

import tornado.ioloop import tornado.web

class MainHandler(tornado.web.RequestHandler): def get(self): self.write("Hello, world")

app = tornado.web.Application([ (r"/", MainHandler)])

app.listen(8888) tornado.ioloop.IOLoop.current().start()

ПОДКЛЮЧЕНИЕ БД

import sqlalchemy as sa from aiopg.sa import create_engine

metadata = sa.MetaData() tbl = sa.Table('tbl', metadata, sa.Column('id', sa.Integer, primary_key=True), sa.Column('val', sa.String(255))) app['db'] = await create_engine(...)

async def close_db(app): app['db'].close() await app['db'].wait_closed() app.on_cleanup.append(close_db)

ЗАПРОСЫ В БД

async def handler(request): txt = "" async with request.app['db'].acquire() as conn: await conn.execute(tbl.insert().values(val='abc'))

async for row in conn.execute(tbl.select()): txt += "{}: {}\n".format(row.id, row.val) return web.Response(text=txt)

СЕРВЕРНЫЕ ВЕБ­СОКЕТЫ

async def handler(request): ws = web.WebSocketResponse() await ws.prepare(request)

async for msg in ws: if msg.data == 'close': await ws.close() break else: ws.send_str(msg.data + '/answer')

return ws

НЕТРИВИАЛЬНЫЕ ШТУЧКИ

ЖИЗНЕННЫЙ ЦИКЛ ЗАПРОСА­ОТВЕТА ИMIDDLEWARES

СЕРВЕРНЫЕ СЕССИИ

from aiohttp_session import get_session

async def hander(request): session = await get_session(request) session['key'] = 'value' return web.Response()

DEBUG TOOLBAR

ASYNCIO ПОД КАПОТОМ

SELECTOR

EVENT_READ/EVENT_WRITE

register(fd, events, data=None)unregister(fd)select(timeout=None)

select, poll, epoll, kqueue

EVENT LOOP

add_reader(fd, callback, *args)remove_reader(fd)add_writer(fd, callback, *args)remove_writer(fd)­­­call_soon(callback, *args)call_later(delay, callback, *args)

ИТЕРАЦИЯ EVENT LOOP

def run_forever(): while True: events = selector.select(timeout) for key, mask in events: if mask & EVENT_READ: key.reader(key.fileobj) if mask & EVENT_WRITE: key.writer(key.fileobj) for handler in ready: handler._run()

БУФЕРЫ СОКЕТА

ТАЙМЕРЫ

ПОЛЕЗНЫЕ СОВЕТЫ

ЦИКЛ РАЗРАБОТКИ

Для разработки ­­ один процессУдобство запуска тестовРазворачивать на отдельных процессах­контейнерах­узлах

PYTHONASYNCIODEBUG=1

async def f(): fut = asyncio.Future() fut.set_exception(RuntimeError()) del fut

... ERROR:asyncio:Future exception was never retrieved future: Future finished exception=RuntimeError() RuntimeError

$ PYTHONASYNCIODEBUG=x python myapp.py

ERROR:asyncio:Future exception was never retrieved future: Future finished exception=RuntimeError() created at filename.py:10 source_traceback: Object created at (most recent call last): ... File "filename.py", line 10, in f fut = asyncio.Future() RuntimeError

EXPLICIT LOOP

def request(method, url, *, loop=None): ...

loop = asyncio.get_event_loop() await request('GET', 'http://python.org', loop=loop)

ТЕСТИРОВАНИЕ

class Test(unittest.TestCase):

def setUp(self): self.loop = asyncio.new_event_loop() asyncio.set_event_loop(None)

def tearDown(self): self.loop.close()

def test_func(self): async def go(): self.assertEqual(1, await func(loop=loop))

self.loop.run_until_complete(go())

ПРОИЗВОДИТЕЛЬНОСТЬ

Как у Tornado или Twisted

Достаточно неплохо для highload

Выдерживает миллионы online пользователей и пр.

ВОПРОСЫ?

АНДРЕЙ СВЕТЛОВ

[email protected]

@andrew_svetlov