Successfully reported this slideshow.

Разработка сетевых приложений с gevent

13,588 views

Published on

Это слайды моего доклада на DevConf 2010.

Published in: Education
  • Slide 10 - в twisted есть @defer.inlineCallbacks, можно пистаь код без колбэков.

    Slide 19 - явное лучше не явного PEP20 Explicit is better than implicit. Считаю что весь gevent с кучей не явной магии, это плохо.
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • Информативные слайды, спасибо.
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • Андрей, спасибо большое за материал. В голове все разложилось по полочкам! :)
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here

Разработка сетевых приложений с gevent

  1. 1. Разработка сетевых приложений с gevent Андрей Попп 8mayday@gmail.com http://braintrace.ru @andreypopp
  2. 2. Сетевые сервисы должны уметь одновременно обрабатывать несколько клиентских запросов. Андрей Попп: Разработка сетевых приложений с gevent
  3. 3. Современные сетевые сервисы должны уметь одновременно обрабатывать огромное количество клиентских запросов. Андрей Попп: Разработка сетевых приложений с gevent
  4. 4. Стратегии организации I/O Основные стратегии обработки соединений относительно организации I/O: Блокирующий I/O – необходимо несколько потоков ОС. Неблокирующий I/O + мултиплексор – достаточно даже одного потока ОС. Андрей Попп: Разработка сетевых приложений с gevent
  5. 5. Блокирующий I/O Необходимо использовать отдельный поток на каждое активное соединение. Много активных соединений = много активных потоков = большое количество потребляемой памяти. Переключение контекста исполнения обходится дорого. В Python есть GIL. Андрей Попп: Разработка сетевых приложений с gevent
  6. 6. Блокирующий I/O Объективно не подходит для обслуживания большого количество одновременных соединений. Андрей Попп: Разработка сетевых приложений с gevent
  7. 7. Неблокирующий I/O Операции на сокетах не блокируют поток – они производяться только тогда, когда доступны. Для обслуживания нескольких активных соединений достаточно даже одного потока. Меньшее количество потребляемой памяти. Обычно приходится выстраивать код приложения ввиде обработчиков событий на сокетах. Андрей Попп: Разработка сетевых приложений с gevent
  8. 8. Неблокирующий I/O Использвование неблокирующего I/O кажется более подходящим решением проблемы. Андрей Попп: Разработка сетевых приложений с gevent
  9. 9. Неблокирующий I/O Но какие распространённые библиотеки/фрэймворки мы имеем для Python: asyncore, Twisted, Tornado. Андрей Попп: Разработка сетевых приложений с gevent
  10. 10. С Twisted приходится писать асинхронный код – это неудобно! def handle_client ( req ): deferred = m a k e _ a p i _ r e q u e s t () deferred . addCallback ( handle_api_resp , req ) deferred . addErrback ( handle_error ) return deferred def h and le _a p i_ re s p ( api_resp , req ): deferred = m ak e _d b_ r eq ue s t () deferred . addCallback ( handle_db_resp , api_resp , req ) return deferred def handle _db_re sp ( db_resp , api_response , req ): # work with api_resp and db_resp request . write (" success ") def handle_error ( failure , req ): # handle error request . write (" error ") return failure Андрей Попп: Разработка сетевых приложений с gevent
  11. 11. Синхронный код писать проще и получается он понятнее. def handle_client ( request ): try : api_response = m a k e _ a p i _ r e q u e s t () db_response = ma k e_ d b_ re q ue st () # work with api_response and db_response request . write (" success ") except Exception : request . write (" error ") raise Но приходится использовать блокирующий I/O. Андрей Попп: Разработка сетевых приложений с gevent
  12. 12. Что делать? Нужно искать компромис! Андрей Попп: Разработка сетевых приложений с gevent
  13. 13. Микропотоки Микропотоки или “зелёные” потоки или userspace-потоки: Это как функции, исполнение которых можно приостановить, а потом – продолжить. Работают внутри одного или нескольких потоков ОС. Для их исполнения необходим планировщик. Обычно дёшевы в плане потребления памяти и переключения контекста. Андрей Попп: Разработка сетевых приложений с gevent
  14. 14. Микропотоки + Неблокирующий I/O Чтобы микропотоки исполнялись им необходим планировщик. Предлагается следующий вариант: Как только микропоток пытается выполнить I/O, он передаёт управление планировщику. После того, как выполнение I/O становится доступным для микропотока – планировщик возвращает ему управление. Андрей Попп: Разработка сетевых приложений с gevent
  15. 15. Блокируется только микропоток, который пытается выполнить I/O, а не весь интерпретатор. Андрей Попп: Разработка сетевых приложений с gevent
  16. 16. Это называется кооперативная многозадачность – потоки сами решают когда передать исполнение другим. Андрей Попп: Разработка сетевых приложений с gevent
  17. 17. Существует также преемптивная или вытесняющая многозадачность – поток вытесняется планировщиком после определённого количества выполненных инструкций или по истичении определённого времени. Андрей Попп: Разработка сетевых приложений с gevent
  18. 18. Но разве микропотоки есть в Python? Андрей Попп: Разработка сетевых приложений с gevent
  19. 19. Микропотоки в Python – Генераторы Можно реализовать микропотоки в Python c помощью генераторов (PEP 342, начиная с версии Python 2.5). Чтобы передать исполнение – делаем yield. К сожалению: Кооперация с помощью yield – это слишком явно и неудобно, приходиться самим думать, когда отдавать управление. Генераторы не сохраняют весь стэк во время остановки – yield должен быть всегда на самом верху. Андрей Попп: Разработка сетевых приложений с gevent
  20. 20. Микропотоки в Python – greenlet Микропотоки с библиотекой greenlet: Микропоток или просто гринлет это объект greenlet. Кооперация посредством вызова метода greenlet.switch. greenlet – это “выжимка” из Stackless Python. Андрей Попп: Разработка сетевых приложений с gevent
  21. 21. Как работают гринлеты from greenlet import greenlet >>> def test1 (): ... print ’one ’ ... gr2 . switch () ... print ’two ’ ... >>> def test2 (): ... print ’ three ’ ... gr1 . switch () ... print ’ four ’ ... >>> gr1 = greenlet ( test1 ) >>> gr2 = greenlet ( test2 ) >>> gr1 . switch () one three two Андрей Попп: Разработка сетевых приложений с gevent
  22. 22. Микропотоки реализованные с помощью greenlet удобны – они не страдают от недостатков генераторов. Андрей Попп: Разработка сетевых приложений с gevent
  23. 23. Теперь нам нужен планировщик, который будет контролировать исполнение гринлетов, руководствуясь событиями I/O. Андрей Попп: Разработка сетевых приложений с gevent
  24. 24. gevent = libevent + greenlet Такой планировщик предоставляет нам библиотека gevent. Андрей Попп: Разработка сетевых приложений с gevent
  25. 25. Почему используется libevent Почему gevent использует libevent для обработки событий: Это быстрая библиотека, написанная на языке C – сам цикл полностью в C коде. Libevent используется длительное время и хорошо себя зарекомендовала (Chromium, Memcached, Io). Предоставляет встраиваемый HTTP-сервер – evhttp. Имеет API для работы с DNS – evdns. Андрей Попп: Разработка сетевых приложений с gevent
  26. 26. Как устроен gevent Общая схема Цикл обработки событий libevent работает в отдельном гринлете – этот гринлет называется хаб. Хаб запускается неявно и только при необходимости. Кооперация между гринлетами происходит через хаб: Гринлет может переключиться только на хаб. Гринлет может получить управление только через хаб. Андрей Попп: Разработка сетевых приложений с gevent
  27. 27. Как устроен gevent Организация I/O Чтобы совершить I/O наш гринлет должен: 1 Отправить запрос на I/O в цикл обработки событий. 2 Переключиться на хаб. 3 Хаб запускает выполнение других гринлетов. 4 ... 5 Как только запрос на I/O выполнен, хаб переключается обратно на наш гринлет. Андрей Попп: Разработка сетевых приложений с gevent
  28. 28. Блокируется только гринлет, который пытается выполнить I/O, а не весь интерпретатор. Андрей Попп: Разработка сетевых приложений с gevent
  29. 29. Сетевой I/O с gevent Чтобы выполнять I/O гринлеты должны использовать кооперативный gevent.socket. Его API полностью повторяет socket стандартной библиотеки Python. Андрей Попп: Разработка сетевых приложений с gevent
  30. 30. Сетевой I/O с gevent Кстати, gevent.socket.getaddrinfo, gevent.socket.gethostbyname используют evdns и тоже являются блокирующими только для вызывающего их гринлета. Андрей Попп: Разработка сетевых приложений с gevent
  31. 31. Пример: конкурентный эхосервер с gevent Реализация from gevent import socket , spawn def serve (( host , port ) , handler ): acceptor = socket . socket ( socket . AF_INET , socket . STREAM ) acceptor . bind (( host , port )) while True : client , address = acceptor . accept () spawn ( handler , client , address ) def handler ( sock , address ): f = sock . makefile () while True : line = f . readline () if not line : break f . write ( line ) f . flush () Андрей Попп: Разработка сетевых приложений с gevent
  32. 32. Пример: конкурентный эхосервер с gevent Обработка соединений Обработка соединения происходит в отдельном гринлете: from gevent import socket , spawn def serve (( host , port ) , handler ): acceptor = socket . socket ( socket . AF_INET , socket . STREAM ) acceptor . bind (( host , port )) while True : client , address = acceptor . accept () spawn(handler, client, address) def handler ( sock , address ): f = sock . makefile () while True : line = f . readline () if not line : break f . write ( line ) f . flush () Андрей Попп: Разработка сетевых приложений с gevent
  33. 33. Пример: конкурентный эхосервер с gevent Точки кооперации В этих точках гринлет отдаёт управление циклу libevent: from gevent import socket , spawn def serve (( host , port ) , handler ): acceptor = socket . socket ( socket . AF_INET , socket . STREAM ) acceptor . bind (( host , port )) while True : client, address = acceptor.accept() spawn ( handler , client , address ) def handler ( sock , address ): f = sock . makefile () while True : line = f.readline() if not line : break f.write(line) f.flush() Андрей Попп: Разработка сетевых приложений с gevent
  34. 34. Оказалось достаточно использовать gevent.socket вместо socket и вызвать gevent.spawn в нужном месте. Андрей Попп: Разработка сетевых приложений с gevent
  35. 35. Пример: конкурентный эхосервер с gevent Используем StreamServer Нужно использовать gevent.server.StreamServer: from gevent . server import StreamServer def handler ( sock , address ): f = sock . makefile () while True : line = f . readline () if not line : break f . write ( line ) f . flush () StreamServer (( ’ localhost ’ , 6000) , handler ). serve_forever () Андрей Попп: Разработка сетевых приложений с gevent
  36. 36. Мы умеем создавать новые гринлеты (gevent.spawn) и использовать gevent.socket. Посмотрим, что ещё мы можем делать с gevent. Андрей Попп: Разработка сетевых приложений с gevent
  37. 37. Базовые возможности gevent Ждём завершения работы гринлета Ждём пока гринлет прекратит свою работу: >>> task = gevent . spawn ( lambda a , b : a + b , 1 , 2) >>> task . join () Если нам нужен результат работы гринлета: >>> task = gevent . spawn ( lambda a , b : a + b , 1 , 2) >>> task . get () 3 В случае, если гринлет прекратил работу из-за исключения: >>> task = gevent . spawn ( lambda a , b : a / b , 1 , 0) >>> task . get () Traceback ( most recent call last ): ... Z e r o D i vi s i o n E r r o r : integer division or modulo by zero Андрей Попп: Разработка сетевых приложений с gevent
  38. 38. Базовые возможности gevent Преждевременное завершение гринлета Чтобы завершить выполнение гринлета: >>> task = gevent . spawn ( lambda a , b : a + b , 1 , 2) >>> task . kill () Андрей Попп: Разработка сетевых приложений с gevent
  39. 39. Базовые возможности gevent Приостанавливаем выполнение гринлета Иногда нужно приостановить выполнение гринлета: >>> def some_work (): ... # do some work ... gevent . sleep (10) ... # continue Функция gevent.sleep аналогична time.sleep, только “засыпает” не весь интерпретатор, а отдельный гринлет. Андрей Попп: Разработка сетевых приложений с gevent
  40. 40. Базовые возможности gevent Обработка таймаутов Обработка таймаутов осуществляется с gevent.Timeout: timeout = Timeout (10) timeout . start () try : # do some work except gevent . Timeout : # handle timeout finally : timeout . cancel () . . . или как контекст-менеджер: try : with gevent . Timeout (10): # do some work except gevent . Timeout : # handle timeout Андрей Попп: Разработка сетевых приложений с gevent
  41. 41. Управляем несколькими гринлетами Объединяем гринлеты в группы Иногда нужно управлять несколькими гринлетами сразу: >>> tasks = gevent . pool . GreenletSet () >>> for i in range (10): ... tasks . spawn ( do_some_work , i ) >>> tasks . join () . . . или. . . >>> tasks = gevent . pool . GreenletSet () >>> tasks . map ( lambda a : a **2 , range (10)) [0 , 1 , 4 , 9 , 16 , 25 , 36 , 49 , 64 , 81] Андрей Попп: Разработка сетевых приложений с gevent
  42. 42. Управляем несколькими гринлетами Работаем с пулом гринлетов А иногда бывает нужно ограничить количество одновременно выполняемых гринлетов в группе: >>> tasks = gevent . pool . Pool ( size =5) >>> for i in range (10): ... tasks . spawn ( do_some_work , i ) >>> tasks . join () В данном случае будет одновременно исполняться только 5 гринлетов. Таким образом можно, например, ограничить количество одновременно обрабатываемых соединений. Андрей Попп: Разработка сетевых приложений с gevent
  43. 43. HTTP-сервисы с gevent Используем evhttp Модуль gevent.http предоставляет API для использования evhttp, но нас больше интересует WSGI. Андрей Попп: Разработка сетевых приложений с gevent
  44. 44. HTTP-сервисы с gevent WSGI сервер Модуль gevent.wsgi – реализация WSGI на базе gevent.http: from gevent . wsgi import WSGIServer def hello_world ( environ , star t_resp onse ): star t_resp onse ( ’200 OK ’ , [( ’ Content - Type ’ , ’ text / html ’)]) return [" It works !"] WSGIServer (( ’ localhost ’ , 8000) , hello_world ). serve_forever () Можно использовать практически любой WSGI фрэймворк/библиотеку: Django, Werkzeug, WebOb, repoze.bfg, Pylons. Андрей Попп: Разработка сетевых приложений с gevent
  45. 45. Используем gevent с другими библиотеками Как уже говорилось, API gevent.socket полностью повторяет socket из стандартной библиотеки Python. Андрей Попп: Разработка сетевых приложений с gevent
  46. 46. Используем gevent с другими библиотеками Предоставляем фабрику кооперативных сокетов Если библиотека позволяет пользовательскому коду подменять класс используемого сокета: from s om e n e t w o r k l i b r a r y import Client from gevent import socket class C o o p e r a t i v e G e v e n t A w a r e C l i e n t ( Client ): def create_socket ( self ): sock = socket . socket ( socket . AF_INET , socket . STREAM ) return sock Но что делать, если не позволяет? Андрей Попп: Разработка сетевых приложений с gevent
  47. 47. Используем gevent с другими библиотеками Monkey patching gevent предоставляет возможность пропатчить модуль socket стандартной библиотеки: from gevent import monkey monkey . patch_socket () После этого, код, который использует модуль socket будет кооперироваться. Андрей Попп: Разработка сетевых приложений с gevent
  48. 48. Используем gevent с другими библиотеками Monkey patching Кроме этого в gevent.monkey: patch_time() – заменяем time.sleep() на кооперативный gevent.sleep(). patch_thread() – создаём гринлеты вместо потоков ОС, также патчит threading.local. patch_os(), patch_ssl(), patch_select() – . . . patch_all() – патчим всё. Андрей Попп: Разработка сетевых приложений с gevent
  49. 49. Пример: используем gevent с urllib2 from gevent . pool import Pool from gevent import monkey monkey . patch_all () import urllib2 tasks = Pool ( size =20) urls = [ ’ http :// www . gevent . org ’ , ...] def print_head ( url ): print ’ Starting %s ’ % url data = urllib2 . urlopen ( url ). read () print ’% s : % s bytes : %r ’ % ( url , len ( data ) , data [:50]) for url in urls : tasks . spawn ( print_head , url ) tasks . join () Андрей Попп: Разработка сетевых приложений с gevent
  50. 50. Используем gevent с другими библиотеками Я также использовал gevent совместно с SQLAlchemy, boto. Андрей Попп: Разработка сетевых приложений с gevent
  51. 51. Где используется gevent Несколько проектов, которые используют gevent: Gunicorn – WSGI HTTP сервер, может использовать gevent для обработки запросов. pastegevent – используем gevent.wsgi для запуска WSGI приложений вместе с PasteDeploy. gevent-mysql – драйвер для MySQL, написанный на Cython, использующий API gevent. psycogreen – отдельная ветка psycopg, которая работает с асинхронными библиотеками, например с gevent. Андрей Попп: Разработка сетевых приложений с gevent
  52. 52. Некоторые ограничения Как это обычно бывает, существуют некоторые ограничения: После os.fork() необходимо вызывать gevent.reinit(). Библиотеку можно использовать только в одном потоке ОС – ограничение libevent 1.4. Блокирующий stdin – вскоре будет исправлено. Библиотеки которые не используют socket блокируют интерпретатор полностью – можно выполнять их в отдельном потоке ОС. Андрей Попп: Разработка сетевых приложений с gevent
  53. 53. Какие темы я не затронул Остались темы, которые я не затронул: Линки между гринлетами. Примитивы синхронизации – gevent.event. Синхронные очереди – gevent.queue. Андрей Попп: Разработка сетевых приложений с gevent
  54. 54. Полезные ссылки http://gevent.org – официальный сайт и документация. http://bitbucket.org/denis/gevent/ – исходный код. http://groups.google.com/group/gevent – рассылка. http://blog.gevent.org/ – блог проекта. http://twitter.com/gevent – twitter проекта. И наконец #gevent на irc.freenode.net. Андрей Попп: Разработка сетевых приложений с gevent
  55. 55. Спасибо! Андрей Попп: Разработка сетевых приложений с gevent

×