RU | EN

Межпроцессная диспетчеризация событий в web-приложениях Python, часть 1

В своей статье о диспетчеризации событий в Python я описал использование пакета PyDispatcher, который используется в качетстве диспетчера событий в среде Django. Это — хороший способ, позволяющий различным частям приложения, которые могут быть не связаны друг с другом непосредственно, успешно взаимодействовать и реагировать на изменения. Такая схема прекрасно работает в том случае, если все приложение функционирует в рамках одного процесса в системе.
Сценарий запуска приложений, основанных на Django, как в режиме FastCGI, так и под mod_python, в большинстве случаев предполагает, что в системе будет запущено несколько независимых процессов python (или модулей python в нескольких процессах сервера Apache). Для простоты изложения я буду описывать только случай FastCGI.
Веб-сервер, отвечающий на запросы из внешнего мира, переправляет запросы какому-либо из запущенных процессов по протоколу TCP/IP или через доменный сокет UNIX, процесс отрабатывает запрос и возвращает серверу результат обработки. При этом, в отличие от ситуации, когда обработка запроса производится встроенным модулем веб-сервера, таким как mod_php в Apache для запуска скриптов на PHP, процесс, выполнивший запрос, не завершается, а остается в системе в ожидании следующего запроса веб-сервера. При этом все модули Python, загруженные в процесс во время обработки запроса, а также все окружение, остаются нетронутыми, и не будут повторно загружаться при обработке следующего запроса.
При этом, ситуация, при которой следующий запрос к веб-серверу будет направлен тому же процессу, не гарантирована — запрос может быть направлен другому процессу, причем, в зависимости от конфигурации FastCGI, этот процесс может физически находиться на другом сервере!
Что если нам требуется наладить диспетчеризацию событий похожим на PyDispatcher образом, но только так, чтобы события могли выходить за рамки сгенерировавшего их процесса, и доставляться другим процессам, действующим в рамках приложения?
Может возникнуть вопрос — зачем все это нужно? В какой-то момент возникает необходимость поддерживать состояние на уровне приложения, подобно тому как в большинстве более-менее полезных веб-приложений существует необходимость поддерживать состояние сеанса клиента. Сеанс клиента связан с данными, присущими этому клиенту, например такими как данные о его авторизации. Состояние приложения связано с данными, общими для всех запросов, вне зависимости от клиента, от которого пришел запрос, например такими как текущий курс доллара. В обоих случаях, состояние обычно используется для кэширования каких-либо параметров, чтобы не повторять процедуру их получения.
Состояние сеанса обычно сохраняется в файлах на диске, базе данных, в разделяемой оперативной памяти и в памяти внешних кэширующих серверов, таких как memcached, т.е. вне процесса. Объекты, которые нужно сохранить, сериализуются в строки, которые затем при следующем запросе могут быть прочитаны программой, и из этих строк могут быть восстановлены работоспособные объекты языка программирования. В случае состояния сеанса такой подход оправдан, т.к. сеансов может быть очень большое количество, и каждый может содержать значительный объем связанных с ним данных, и при этом каждый следующий запрос сеанса может быть обработан разными процессами. Нет смысла бесконечно держать в пространстве процесса данные всех сеансов, которые ему посчастливилось обработать.
Состояние приложения можно поддерживать таким же образом. Однако, в случае долгоживущих процессов, таких как процессы веб-приложений на Python, работающие по протоколу FastCGI, такой подход может быть не вполне оправдан. Во-первых, сериализация и сохранение параметров приложения по завершении запроса, а затем чтение их из хранилища и восстановление при следующем запросе - лишняя и довольно непростая работа для такого рода приложения. Причины, в частности, следующие:

  • Данные состояния приложения не так подвержены изменениям, как данные текущего сеанса, и в большинстве случаев восстановленные объекты будут идентичны тем, которые незадолго до этого были сохранены.
  • Конкуренция между процессами, сохраняющими данные приложения по завершении процесса и читающими сериализованные данные приложения в начале обработки запроса гораздо выше конкуренции между процессами, обслуживающими конкретный сеанс, т.к. запросы сеанса обычно последовательны — следующий запрос обычно приходит после завершения предыдущего. В то же время, изменение данных на уровне приложения может происходить в момент параллельной работы многих процессов, обрабатывающих запросы разных сеансов.
  • Состояние приложения может содержать иерархии ссылающихся друг на друга объектов. При изменениии какого-либо из объектов в одном процессе гораздо выгоднее дать сигнал аналогичному объекту в других процессах изменить свое состояние, вместо того чтобы перегружать всю иерархию.
Следовательно, оптимальный сценарий такой: процесс, в котором произошли изменения общих данных приложения, записывает эти изменения в общее хранилище и каким-то образом оповещает другие процессы об изменениях. Сценарий весьма похож на то, каким образом происходит диспетчеризация событий внутри процессов с использованием PyDispatcher, не так ли? Однако как преодолеть границы этого процесса, и послать событие другим процессам приложения?
Об этом в следующей статье.

Источники:
www.ragbag.ru/2006/09/27/python_netdispatcher1/

Автор: Деркачев М.   

Комментарии: