SlideShare a Scribd company logo
1 of 55
Download to read offline
Разработка сетевых приложений с gevent


                       Андрей Попп




8mayday@gmail.com
http://braintrace.ru
@andreypopp
Сетевые сервисы должны уметь одновременно обрабатывать
                      несколько клиентских запросов.




Андрей Попп: Разработка сетевых приложений с gevent
Современные сетевые сервисы должны уметь одновременно
          обрабатывать огромное количество клиентских запросов.




Андрей Попп: Разработка сетевых приложений с gevent
Стратегии организации I/O




      Основные стратегии обработки соединений относительно
      организации I/O:

             Блокирующий I/O – необходимо несколько потоков ОС.
             Неблокирующий I/O + мултиплексор – достаточно даже
             одного потока ОС.




Андрей Попп: Разработка сетевых приложений с gevent
Блокирующий I/O




             Необходимо использовать отдельный поток на каждое
             активное соединение.
             Много активных соединений = много активных потоков =
             большое количество потребляемой памяти.
             Переключение контекста исполнения обходится дорого.
             В Python есть GIL.




Андрей Попп: Разработка сетевых приложений с gevent
Блокирующий I/O




             Объективно не подходит для обслуживания большого
                   количество одновременных соединений.




Андрей Попп: Разработка сетевых приложений с gevent
Неблокирующий I/O




             Операции на сокетах не блокируют поток – они
             производяться только тогда, когда доступны.
             Для обслуживания нескольких активных соединений
             достаточно даже одного потока.
             Меньшее количество потребляемой памяти.
             Обычно приходится выстраивать код приложения ввиде
             обработчиков событий на сокетах.




Андрей Попп: Разработка сетевых приложений с gevent
Неблокирующий I/O




               Использвование неблокирующего I/O кажется более
                       подходящим решением проблемы.




Андрей Попп: Разработка сетевых приложений с gevent
Неблокирующий I/O




      Но какие распространённые библиотеки/фрэймворки мы имеем
                  для Python: asyncore, Twisted, Tornado.




Андрей Попп: Разработка сетевых приложений с gevent
С 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
Синхронный код писать проще и получается он понятнее.


       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
Что делать? Нужно искать компромис!




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



      Микропотоки или “зелёные” потоки или userspace-потоки:

             Это как функции, исполнение которых можно
             приостановить, а потом – продолжить.
             Работают внутри одного или нескольких потоков ОС.
             Для их исполнения необходим планировщик.
             Обычно дёшевы в плане потребления памяти и
             переключения контекста.




Андрей Попп: Разработка сетевых приложений с gevent
Микропотоки + Неблокирующий I/O



      Чтобы микропотоки исполнялись им необходим планировщик.
      Предлагается следующий вариант:

             Как только микропоток пытается выполнить I/O, он
             передаёт управление планировщику.
             После того, как выполнение I/O становится доступным для
             микропотока – планировщик возвращает ему управление.




Андрей Попп: Разработка сетевых приложений с gevent
Блокируется только микропоток, который пытается выполнить
                      I/O, а не весь интерпретатор.




Андрей Попп: Разработка сетевых приложений с gevent
Это называется кооперативная многозадачность – потоки сами
                решают когда передать исполнение другим.




Андрей Попп: Разработка сетевых приложений с gevent
Существует также преемптивная или вытесняющая
         многозадачность – поток вытесняется планировщиком после
         определённого количества выполненных инструкций или по
                    истичении определённого времени.




Андрей Попп: Разработка сетевых приложений с gevent
Но разве микропотоки есть в Python?




Андрей Попп: Разработка сетевых приложений с gevent
Микропотоки в Python – Генераторы


      Можно реализовать микропотоки в Python c помощью
      генераторов (PEP 342, начиная с версии Python 2.5).
      Чтобы передать исполнение – делаем yield.
      К сожалению:

             Кооперация с помощью yield – это слишком явно и
             неудобно, приходиться самим думать, когда отдавать
             управление.
             Генераторы не сохраняют весь стэк во время остановки –
             yield должен быть всегда на самом верху.




Андрей Попп: Разработка сетевых приложений с gevent
Микропотоки в Python – greenlet




      Микропотоки с библиотекой greenlet:

             Микропоток или просто гринлет это объект greenlet.
             Кооперация посредством вызова метода greenlet.switch.
             greenlet – это “выжимка” из Stackless Python.




Андрей Попп: Разработка сетевых приложений с gevent
Как работают гринлеты

       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
Микропотоки реализованные с помощью greenlet удобны – они
                не страдают от недостатков генераторов.




Андрей Попп: Разработка сетевых приложений с gevent
Теперь нам нужен планировщик, который будет контролировать
          исполнение гринлетов, руководствуясь событиями I/O.




Андрей Попп: Разработка сетевых приложений с gevent
gevent = libevent + greenlet




          Такой планировщик предоставляет нам библиотека gevent.




Андрей Попп: Разработка сетевых приложений с gevent
Почему используется libevent



      Почему gevent использует libevent для обработки событий:

             Это быстрая библиотека, написанная на языке C – сам
             цикл полностью в C коде.
             Libevent используется длительное время и хорошо себя
             зарекомендовала (Chromium, Memcached, Io).
             Предоставляет встраиваемый HTTP-сервер – evhttp.
             Имеет API для работы с DNS – evdns.




Андрей Попп: Разработка сетевых приложений с gevent
Как устроен gevent
 Общая схема




             Цикл обработки событий libevent работает в отдельном
             гринлете – этот гринлет называется хаб.
             Хаб запускается неявно и только при необходимости.
             Кооперация между гринлетами происходит через хаб:
                   Гринлет может переключиться только на хаб.
                   Гринлет может получить управление только через хаб.




Андрей Попп: Разработка сетевых приложений с gevent
Как устроен gevent
 Организация I/O




      Чтобы совершить I/O наш гринлет должен:

         1   Отправить запрос на I/O в цикл обработки событий.
         2   Переключиться на хаб.
         3   Хаб запускает выполнение других гринлетов.
         4   ...
         5   Как только запрос на I/O выполнен, хаб переключается
             обратно на наш гринлет.




Андрей Попп: Разработка сетевых приложений с gevent
Блокируется только гринлет, который пытается выполнить I/O,
                         а не весь интерпретатор.




Андрей Попп: Разработка сетевых приложений с gevent
Сетевой I/O с gevent




           Чтобы выполнять I/O гринлеты должны использовать
        кооперативный gevent.socket. Его API полностью повторяет
                  socket стандартной библиотеки Python.




Андрей Попп: Разработка сетевых приложений с gevent
Сетевой I/O с gevent




                   Кстати, gevent.socket.getaddrinfo,
           gevent.socket.gethostbyname используют evdns и тоже
            являются блокирующими только для вызывающего их
                                гринлета.




Андрей Попп: Разработка сетевых приложений с gevent
Пример: конкурентный эхосервер с 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
Пример: конкурентный эхосервер с 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
Пример: конкурентный эхосервер с 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
Оказалось достаточно использовать gevent.socket вместо
             socket и вызвать gevent.spawn в нужном месте.




Андрей Попп: Разработка сетевых приложений с gevent
Пример: конкурентный эхосервер с 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
Мы умеем создавать новые гринлеты (gevent.spawn) и
       использовать gevent.socket. Посмотрим, что ещё мы можем
                            делать с gevent.




Андрей Попп: Разработка сетевых приложений с gevent
Базовые возможности 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
Базовые возможности gevent
 Преждевременное завершение гринлета




      Чтобы завершить выполнение гринлета:
       >>> task = gevent . spawn ( lambda a , b : a + b , 1 , 2)
       >>> task . kill ()




Андрей Попп: Разработка сетевых приложений с gevent
Базовые возможности gevent
 Приостанавливаем выполнение гринлета




      Иногда нужно приостановить выполнение гринлета:
       >>> def some_work ():
       ...    # do some work
       ...    gevent . sleep (10)
       ...    # continue



      Функция gevent.sleep аналогична time.sleep, только
      “засыпает” не весь интерпретатор, а отдельный гринлет.




Андрей Попп: Разработка сетевых приложений с gevent
Базовые возможности 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
Управляем несколькими гринлетами
 Объединяем гринлеты в группы




      Иногда нужно управлять несколькими гринлетами сразу:
       >>> 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
Управляем несколькими гринлетами
 Работаем с пулом гринлетов



      А иногда бывает нужно ограничить количество одновременно
      выполняемых гринлетов в группе:
       >>> tasks = gevent . pool . Pool ( size =5)
       >>> for i in range (10):
       ...     tasks . spawn ( do_some_work , i )
       >>> tasks . join ()



      В данном случае будет одновременно исполняться только 5
      гринлетов.
      Таким образом можно, например, ограничить количество
      одновременно обрабатываемых соединений.



Андрей Попп: Разработка сетевых приложений с gevent
HTTP-сервисы с gevent
 Используем evhttp




         Модуль gevent.http предоставляет API для использования
                  evhttp, но нас больше интересует WSGI.




Андрей Попп: Разработка сетевых приложений с gevent
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
Используем gevent с другими библиотеками




       Как уже говорилось, API gevent.socket полностью повторяет
                socket из стандартной библиотеки Python.




Андрей Попп: Разработка сетевых приложений с gevent
Используем 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
Используем gevent с другими библиотеками
 Monkey patching




      gevent предоставляет возможность пропатчить модуль socket
      стандартной библиотеки:
       from gevent import monkey
       monkey . patch_socket ()



      После этого, код, который использует модуль socket будет
      кооперироваться.




Андрей Попп: Разработка сетевых приложений с gevent
Используем gevent с другими библиотеками
 Monkey patching




      Кроме этого в gevent.monkey:

             patch_time() – заменяем time.sleep() на
             кооперативный gevent.sleep().
             patch_thread() – создаём гринлеты вместо потоков ОС,
             также патчит threading.local.
             patch_os(), patch_ssl(), patch_select() – . . .
             patch_all() – патчим всё.




Андрей Попп: Разработка сетевых приложений с gevent
Пример: используем 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
Используем gevent с другими библиотеками




         Я также использовал gevent совместно с SQLAlchemy, boto.




Андрей Попп: Разработка сетевых приложений с gevent
Где используется gevent


      Несколько проектов, которые используют gevent:

             Gunicorn – WSGI HTTP сервер, может использовать gevent
             для обработки запросов.
             pastegevent – используем gevent.wsgi для запуска WSGI
             приложений вместе с PasteDeploy.
             gevent-mysql – драйвер для MySQL, написанный на Cython,
             использующий API gevent.
             psycogreen – отдельная ветка psycopg, которая работает с
             асинхронными библиотеками, например с gevent.




Андрей Попп: Разработка сетевых приложений с gevent
Некоторые ограничения



      Как это обычно бывает, существуют некоторые ограничения:

             После os.fork() необходимо вызывать gevent.reinit().
             Библиотеку можно использовать только в одном потоке
             ОС – ограничение libevent 1.4.
             Блокирующий stdin – вскоре будет исправлено.
             Библиотеки которые не используют socket блокируют
             интерпретатор полностью – можно выполнять их в
             отдельном потоке ОС.




Андрей Попп: Разработка сетевых приложений с gevent
Какие темы я не затронул




      Остались темы, которые я не затронул:

             Линки между гринлетами.
             Примитивы синхронизации – gevent.event.
             Синхронные очереди – gevent.queue.




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



      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
Спасибо!




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

More Related Content

Viewers also liked

Pyton – пробуем функциональный стиль
Pyton – пробуем функциональный стильPyton – пробуем функциональный стиль
Pyton – пробуем функциональный стильPython Meetup
 
Разработка web-приложений с repoze.bfg
Разработка web-приложений с repoze.bfgРазработка web-приложений с repoze.bfg
Разработка web-приложений с repoze.bfgAndrey Popp
 
About Those Python Async Concurrent Frameworks - Fantix @ OSTC 2014
About Those Python Async Concurrent Frameworks - Fantix @ OSTC 2014About Those Python Async Concurrent Frameworks - Fantix @ OSTC 2014
About Those Python Async Concurrent Frameworks - Fantix @ OSTC 2014Fantix King 王川
 
Dictionary в Python. По мотивам Objects/dictnotes.txt
Dictionary в Python. По мотивам Objects/dictnotes.txtDictionary в Python. По мотивам Objects/dictnotes.txt
Dictionary в Python. По мотивам Objects/dictnotes.txtPython Meetup
 
Scaling Django with gevent
Scaling Django with geventScaling Django with gevent
Scaling Django with geventMahendra M
 
"Внутренности" CPython, часть II
"Внутренности" CPython, часть II"Внутренности" CPython, часть II
"Внутренности" CPython, часть IIPython Meetup
 
Async programming and python
Async programming and pythonAsync programming and python
Async programming and pythonChetan Giridhar
 
Лекция 13. Многопоточность и GIL
Лекция 13. Многопоточность и GILЛекция 13. Многопоточность и GIL
Лекция 13. Многопоточность и GILRoman Brovko
 

Viewers also liked (10)

Pyton – пробуем функциональный стиль
Pyton – пробуем функциональный стильPyton – пробуем функциональный стиль
Pyton – пробуем функциональный стиль
 
Разработка web-приложений с repoze.bfg
Разработка web-приложений с repoze.bfgРазработка web-приложений с repoze.bfg
Разработка web-приложений с repoze.bfg
 
About Those Python Async Concurrent Frameworks - Fantix @ OSTC 2014
About Those Python Async Concurrent Frameworks - Fantix @ OSTC 2014About Those Python Async Concurrent Frameworks - Fantix @ OSTC 2014
About Those Python Async Concurrent Frameworks - Fantix @ OSTC 2014
 
Dictionary в Python. По мотивам Objects/dictnotes.txt
Dictionary в Python. По мотивам Objects/dictnotes.txtDictionary в Python. По мотивам Objects/dictnotes.txt
Dictionary в Python. По мотивам Objects/dictnotes.txt
 
Scaling Django with gevent
Scaling Django with geventScaling Django with gevent
Scaling Django with gevent
 
"Внутренности" CPython, часть II
"Внутренности" CPython, часть II"Внутренности" CPython, часть II
"Внутренности" CPython, часть II
 
Asyncio
AsyncioAsyncio
Asyncio
 
Async programming and python
Async programming and pythonAsync programming and python
Async programming and python
 
Лекция 13. Многопоточность и GIL
Лекция 13. Многопоточность и GILЛекция 13. Многопоточность и GIL
Лекция 13. Многопоточность и GIL
 
Understanding greenlet
Understanding greenletUnderstanding greenlet
Understanding greenlet
 

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

Другая виртуализация
Другая виртуализацияДругая виртуализация
Другая виртуализацияYandex
 
Воюем за ресурсы (ZFConf2011)
Воюем за ресурсы (ZFConf2011)Воюем за ресурсы (ZFConf2011)
Воюем за ресурсы (ZFConf2011)Alexey Kachayev
 
ZFConf 2011: Воюем за ресурсы: Повышение производительности Zend Framework пр...
ZFConf 2011: Воюем за ресурсы: Повышение производительности Zend Framework пр...ZFConf 2011: Воюем за ресурсы: Повышение производительности Zend Framework пр...
ZFConf 2011: Воюем за ресурсы: Повышение производительности Zend Framework пр...ZFConf Conference
 
Android - 11 - Multithreading
Android - 11 - MultithreadingAndroid - 11 - Multithreading
Android - 11 - MultithreadingNoveo
 
Про асинхронное сетевое программирование
Про асинхронное сетевое программированиеПро асинхронное сетевое программирование
Про асинхронное сетевое программированиеPython Meetup
 
Компиляция скриптов PHP (Алексей Романенко)
Компиляция скриптов PHP (Алексей Романенко)Компиляция скриптов PHP (Алексей Романенко)
Компиляция скриптов PHP (Алексей Романенко)Ontico
 
Баннерокрутилка на Erlang
Баннерокрутилка на ErlangБаннерокрутилка на Erlang
Баннерокрутилка на ErlangArtyom Gavrichenkov
 
Egor Fedorov "Behavior-driven development in Python"
Egor Fedorov "Behavior-driven development in Python"Egor Fedorov "Behavior-driven development in Python"
Egor Fedorov "Behavior-driven development in Python"Fwdays
 
Modern neural net architectures - Year 2019 version
Modern neural net architectures - Year 2019 versionModern neural net architectures - Year 2019 version
Modern neural net architectures - Year 2019 versionGrigory Sapunov
 
REPL в Node.js: улучшаем быт разработчик
REPL в Node.js: улучшаем быт разработчикREPL в Node.js: улучшаем быт разработчик
REPL в Node.js: улучшаем быт разработчикIT61
 
Vladimir V Perepelitsa Ae Highload
Vladimir V  Perepelitsa Ae HighloadVladimir V  Perepelitsa Ae Highload
Vladimir V Perepelitsa Ae Highloadrit2010
 
Nikita Galkin "5 production Node.js stories"
Nikita Galkin "5 production Node.js stories"Nikita Galkin "5 production Node.js stories"
Nikita Galkin "5 production Node.js stories"OdessaJS Conf
 
Эволюционный дизайн. Joker Students Day 2016
Эволюционный дизайн. Joker Students Day 2016Эволюционный дизайн. Joker Students Day 2016
Эволюционный дизайн. Joker Students Day 2016Кирилл Толкачёв
 
Apache.JMeter для .NET-проектов
Apache.JMeter для .NET-проектовApache.JMeter для .NET-проектов
Apache.JMeter для .NET-проектовSQALab
 

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

Другая виртуализация
Другая виртуализацияДругая виртуализация
Другая виртуализация
 
JPHP
JPHPJPHP
JPHP
 
Отладка в Python: 2016 edition
Отладка в Python: 2016 editionОтладка в Python: 2016 edition
Отладка в Python: 2016 edition
 
Воюем за ресурсы (ZFConf2011)
Воюем за ресурсы (ZFConf2011)Воюем за ресурсы (ZFConf2011)
Воюем за ресурсы (ZFConf2011)
 
ZFConf 2011: Воюем за ресурсы: Повышение производительности Zend Framework пр...
ZFConf 2011: Воюем за ресурсы: Повышение производительности Zend Framework пр...ZFConf 2011: Воюем за ресурсы: Повышение производительности Zend Framework пр...
ZFConf 2011: Воюем за ресурсы: Повышение производительности Zend Framework пр...
 
Android - 11 - Multithreading
Android - 11 - MultithreadingAndroid - 11 - Multithreading
Android - 11 - Multithreading
 
Про асинхронное сетевое программирование
Про асинхронное сетевое программированиеПро асинхронное сетевое программирование
Про асинхронное сетевое программирование
 
Компиляция скриптов PHP (Алексей Романенко)
Компиляция скриптов PHP (Алексей Романенко)Компиляция скриптов PHP (Алексей Романенко)
Компиляция скриптов PHP (Алексей Романенко)
 
Баннерокрутилка на Erlang
Баннерокрутилка на ErlangБаннерокрутилка на Erlang
Баннерокрутилка на Erlang
 
php frameworks
php frameworksphp frameworks
php frameworks
 
Egor Fedorov "Behavior-driven development in Python"
Egor Fedorov "Behavior-driven development in Python"Egor Fedorov "Behavior-driven development in Python"
Egor Fedorov "Behavior-driven development in Python"
 
Lirc или домашний медиацентр
Lirc или домашний медиацентрLirc или домашний медиацентр
Lirc или домашний медиацентр
 
Modern neural net architectures - Year 2019 version
Modern neural net architectures - Year 2019 versionModern neural net architectures - Year 2019 version
Modern neural net architectures - Year 2019 version
 
REPL в Node.js: улучшаем быт разработчик
REPL в Node.js: улучшаем быт разработчикREPL в Node.js: улучшаем быт разработчик
REPL в Node.js: улучшаем быт разработчик
 
Vladimir V Perepelitsa Ae Highload
Vladimir V  Perepelitsa Ae HighloadVladimir V  Perepelitsa Ae Highload
Vladimir V Perepelitsa Ae Highload
 
Nikita Galkin "5 production Node.js stories"
Nikita Galkin "5 production Node.js stories"Nikita Galkin "5 production Node.js stories"
Nikita Galkin "5 production Node.js stories"
 
DDOS mitigation software solutions
DDOS mitigation software solutionsDDOS mitigation software solutions
DDOS mitigation software solutions
 
Эволюционный дизайн. Joker Students Day 2016
Эволюционный дизайн. Joker Students Day 2016Эволюционный дизайн. Joker Students Day 2016
Эволюционный дизайн. Joker Students Day 2016
 
Apache.JMeter для .NET-проектов
Apache.JMeter для .NET-проектовApache.JMeter для .NET-проектов
Apache.JMeter для .NET-проектов
 
Git for you
Git for youGit for you
Git for you
 

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

  • 1. Разработка сетевых приложений с gevent Андрей Попп 8mayday@gmail.com http://braintrace.ru @andreypopp
  • 2. Сетевые сервисы должны уметь одновременно обрабатывать несколько клиентских запросов. Андрей Попп: Разработка сетевых приложений с gevent
  • 3. Современные сетевые сервисы должны уметь одновременно обрабатывать огромное количество клиентских запросов. Андрей Попп: Разработка сетевых приложений с gevent
  • 4. Стратегии организации I/O Основные стратегии обработки соединений относительно организации I/O: Блокирующий I/O – необходимо несколько потоков ОС. Неблокирующий I/O + мултиплексор – достаточно даже одного потока ОС. Андрей Попп: Разработка сетевых приложений с gevent
  • 5. Блокирующий I/O Необходимо использовать отдельный поток на каждое активное соединение. Много активных соединений = много активных потоков = большое количество потребляемой памяти. Переключение контекста исполнения обходится дорого. В Python есть GIL. Андрей Попп: Разработка сетевых приложений с gevent
  • 6. Блокирующий I/O Объективно не подходит для обслуживания большого количество одновременных соединений. Андрей Попп: Разработка сетевых приложений с gevent
  • 7. Неблокирующий I/O Операции на сокетах не блокируют поток – они производяться только тогда, когда доступны. Для обслуживания нескольких активных соединений достаточно даже одного потока. Меньшее количество потребляемой памяти. Обычно приходится выстраивать код приложения ввиде обработчиков событий на сокетах. Андрей Попп: Разработка сетевых приложений с gevent
  • 8. Неблокирующий I/O Использвование неблокирующего I/O кажется более подходящим решением проблемы. Андрей Попп: Разработка сетевых приложений с gevent
  • 9. Неблокирующий I/O Но какие распространённые библиотеки/фрэймворки мы имеем для Python: asyncore, Twisted, Tornado. Андрей Попп: Разработка сетевых приложений с gevent
  • 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. Синхронный код писать проще и получается он понятнее. 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. Что делать? Нужно искать компромис! Андрей Попп: Разработка сетевых приложений с gevent
  • 13. Микропотоки Микропотоки или “зелёные” потоки или userspace-потоки: Это как функции, исполнение которых можно приостановить, а потом – продолжить. Работают внутри одного или нескольких потоков ОС. Для их исполнения необходим планировщик. Обычно дёшевы в плане потребления памяти и переключения контекста. Андрей Попп: Разработка сетевых приложений с gevent
  • 14. Микропотоки + Неблокирующий I/O Чтобы микропотоки исполнялись им необходим планировщик. Предлагается следующий вариант: Как только микропоток пытается выполнить I/O, он передаёт управление планировщику. После того, как выполнение I/O становится доступным для микропотока – планировщик возвращает ему управление. Андрей Попп: Разработка сетевых приложений с gevent
  • 15. Блокируется только микропоток, который пытается выполнить I/O, а не весь интерпретатор. Андрей Попп: Разработка сетевых приложений с gevent
  • 16. Это называется кооперативная многозадачность – потоки сами решают когда передать исполнение другим. Андрей Попп: Разработка сетевых приложений с gevent
  • 17. Существует также преемптивная или вытесняющая многозадачность – поток вытесняется планировщиком после определённого количества выполненных инструкций или по истичении определённого времени. Андрей Попп: Разработка сетевых приложений с gevent
  • 18. Но разве микропотоки есть в Python? Андрей Попп: Разработка сетевых приложений с gevent
  • 19. Микропотоки в Python – Генераторы Можно реализовать микропотоки в Python c помощью генераторов (PEP 342, начиная с версии Python 2.5). Чтобы передать исполнение – делаем yield. К сожалению: Кооперация с помощью yield – это слишком явно и неудобно, приходиться самим думать, когда отдавать управление. Генераторы не сохраняют весь стэк во время остановки – yield должен быть всегда на самом верху. Андрей Попп: Разработка сетевых приложений с gevent
  • 20. Микропотоки в Python – greenlet Микропотоки с библиотекой greenlet: Микропоток или просто гринлет это объект greenlet. Кооперация посредством вызова метода greenlet.switch. greenlet – это “выжимка” из Stackless Python. Андрей Попп: Разработка сетевых приложений с gevent
  • 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. Микропотоки реализованные с помощью greenlet удобны – они не страдают от недостатков генераторов. Андрей Попп: Разработка сетевых приложений с gevent
  • 23. Теперь нам нужен планировщик, который будет контролировать исполнение гринлетов, руководствуясь событиями I/O. Андрей Попп: Разработка сетевых приложений с gevent
  • 24. gevent = libevent + greenlet Такой планировщик предоставляет нам библиотека gevent. Андрей Попп: Разработка сетевых приложений с gevent
  • 25. Почему используется libevent Почему gevent использует libevent для обработки событий: Это быстрая библиотека, написанная на языке C – сам цикл полностью в C коде. Libevent используется длительное время и хорошо себя зарекомендовала (Chromium, Memcached, Io). Предоставляет встраиваемый HTTP-сервер – evhttp. Имеет API для работы с DNS – evdns. Андрей Попп: Разработка сетевых приложений с gevent
  • 26. Как устроен gevent Общая схема Цикл обработки событий libevent работает в отдельном гринлете – этот гринлет называется хаб. Хаб запускается неявно и только при необходимости. Кооперация между гринлетами происходит через хаб: Гринлет может переключиться только на хаб. Гринлет может получить управление только через хаб. Андрей Попп: Разработка сетевых приложений с gevent
  • 27. Как устроен gevent Организация I/O Чтобы совершить I/O наш гринлет должен: 1 Отправить запрос на I/O в цикл обработки событий. 2 Переключиться на хаб. 3 Хаб запускает выполнение других гринлетов. 4 ... 5 Как только запрос на I/O выполнен, хаб переключается обратно на наш гринлет. Андрей Попп: Разработка сетевых приложений с gevent
  • 28. Блокируется только гринлет, который пытается выполнить I/O, а не весь интерпретатор. Андрей Попп: Разработка сетевых приложений с gevent
  • 29. Сетевой I/O с gevent Чтобы выполнять I/O гринлеты должны использовать кооперативный gevent.socket. Его API полностью повторяет socket стандартной библиотеки Python. Андрей Попп: Разработка сетевых приложений с gevent
  • 30. Сетевой I/O с gevent Кстати, gevent.socket.getaddrinfo, gevent.socket.gethostbyname используют evdns и тоже являются блокирующими только для вызывающего их гринлета. Андрей Попп: Разработка сетевых приложений с gevent
  • 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. Пример: конкурентный эхосервер с 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. Пример: конкурентный эхосервер с 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. Оказалось достаточно использовать gevent.socket вместо socket и вызвать gevent.spawn в нужном месте. Андрей Попп: Разработка сетевых приложений с gevent
  • 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. Мы умеем создавать новые гринлеты (gevent.spawn) и использовать gevent.socket. Посмотрим, что ещё мы можем делать с gevent. Андрей Попп: Разработка сетевых приложений с gevent
  • 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. Базовые возможности gevent Преждевременное завершение гринлета Чтобы завершить выполнение гринлета: >>> task = gevent . spawn ( lambda a , b : a + b , 1 , 2) >>> task . kill () Андрей Попп: Разработка сетевых приложений с gevent
  • 39. Базовые возможности gevent Приостанавливаем выполнение гринлета Иногда нужно приостановить выполнение гринлета: >>> def some_work (): ... # do some work ... gevent . sleep (10) ... # continue Функция gevent.sleep аналогична time.sleep, только “засыпает” не весь интерпретатор, а отдельный гринлет. Андрей Попп: Разработка сетевых приложений с gevent
  • 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. Управляем несколькими гринлетами Объединяем гринлеты в группы Иногда нужно управлять несколькими гринлетами сразу: >>> 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. Управляем несколькими гринлетами Работаем с пулом гринлетов А иногда бывает нужно ограничить количество одновременно выполняемых гринлетов в группе: >>> tasks = gevent . pool . Pool ( size =5) >>> for i in range (10): ... tasks . spawn ( do_some_work , i ) >>> tasks . join () В данном случае будет одновременно исполняться только 5 гринлетов. Таким образом можно, например, ограничить количество одновременно обрабатываемых соединений. Андрей Попп: Разработка сетевых приложений с gevent
  • 43. HTTP-сервисы с gevent Используем evhttp Модуль gevent.http предоставляет API для использования evhttp, но нас больше интересует WSGI. Андрей Попп: Разработка сетевых приложений с gevent
  • 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. Используем gevent с другими библиотеками Как уже говорилось, API gevent.socket полностью повторяет socket из стандартной библиотеки Python. Андрей Попп: Разработка сетевых приложений с gevent
  • 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. Используем gevent с другими библиотеками Monkey patching gevent предоставляет возможность пропатчить модуль socket стандартной библиотеки: from gevent import monkey monkey . patch_socket () После этого, код, который использует модуль socket будет кооперироваться. Андрей Попп: Разработка сетевых приложений с gevent
  • 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. Пример: используем 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. Используем gevent с другими библиотеками Я также использовал gevent совместно с SQLAlchemy, boto. Андрей Попп: Разработка сетевых приложений с gevent
  • 51. Где используется gevent Несколько проектов, которые используют gevent: Gunicorn – WSGI HTTP сервер, может использовать gevent для обработки запросов. pastegevent – используем gevent.wsgi для запуска WSGI приложений вместе с PasteDeploy. gevent-mysql – драйвер для MySQL, написанный на Cython, использующий API gevent. psycogreen – отдельная ветка psycopg, которая работает с асинхронными библиотеками, например с gevent. Андрей Попп: Разработка сетевых приложений с gevent
  • 52. Некоторые ограничения Как это обычно бывает, существуют некоторые ограничения: После os.fork() необходимо вызывать gevent.reinit(). Библиотеку можно использовать только в одном потоке ОС – ограничение libevent 1.4. Блокирующий stdin – вскоре будет исправлено. Библиотеки которые не используют socket блокируют интерпретатор полностью – можно выполнять их в отдельном потоке ОС. Андрей Попп: Разработка сетевых приложений с gevent
  • 53. Какие темы я не затронул Остались темы, которые я не затронул: Линки между гринлетами. Примитивы синхронизации – gevent.event. Синхронные очереди – gevent.queue. Андрей Попп: Разработка сетевых приложений с gevent
  • 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. Спасибо! Андрей Попп: Разработка сетевых приложений с gevent