Ответ 1
Существует несколько способов поддержки многопроцессорной работы для приложения Twisted. Однако одним из важных вопросов для ответа является то, что вы ожидаете от своей модели concurrency и того, как ваше приложение работает с общим состоянием.
В одном процессе Twisted application, concurrency является все кооперативным (с помощью API-интерфейсов Twisted asynchronous I/O), а общее состояние может храниться в любом месте объекта Python. Ваш код приложения работает, зная, что до тех пор, пока он не откажется от контроля, больше ничего не будет запущено. Кроме того, любая часть вашего приложения, которая хочет получить доступ к какой-либо части общего состояния, может, вероятно, сделать это довольно легко, поскольку это состояние, вероятно, хранится в скучном старом объекте Python, к которому легко получить доступ.
Если у вас есть несколько процессов, даже если все запущенные приложения на основе Twisted, то у вас есть две формы concurrency. Один из них аналогичен предыдущему случаю - в рамках конкретного процесса concurrency является кооперативным. Тем не менее, у вас новый вид, в котором работают несколько процессов. Планировщик планирования платформы может переключать выполнение между этими процессами в любое время, и у вас очень мало контроля над этим (а также очень мало видимости того, когда это произойдет). Он может даже планировать два из ваших процессов одновременно работать на разных ядрах (возможно, это даже то, на что вы надеетесь). Это означает, что вы теряете некоторые гарантии относительно согласованности, поскольку один процесс не знает, когда второй процесс может прийти и попытаться работать в каком-то общем состоянии. Это приводит к другой важной области рассмотрения, как вы фактически разделяете состояние между процессами.
В отличие от единой модели процесса, у вас больше нет удобных, легкодоступных мест для хранения вашего состояния, где весь ваш код может достичь этого. Если вы поместите его в один процесс, весь код в этом процессе может легко получить доступ к нему как к обычным объектам Python, но любой код, запущенный в любом из ваших других процессов, больше не имеет легкого доступа к нему. Возможно, вам придется найти систему RPC, чтобы ваши процессы взаимодействовали друг с другом. Или вы можете архивировать свой процесс, чтобы каждый процесс получал запросы, которые требуют сохранения состояния в этом процессе. Примером этого может быть веб-сайт с сеансами, где все состояние о пользователе хранится в их сеансе, а их сеансы идентифицируются с помощью файлов cookie. Интерфейсный процесс может получать веб-запросы, проверять куки файлы, искать, какой серверный процесс отвечает за этот сеанс, а затем перенаправить запрос на этот внутренний процесс. Эта схема означает, что back-end обычно не требуется связываться (пока ваше веб-приложение достаточно простое - то есть, пока пользователи не взаимодействуют друг с другом или не работают с общими данными).
Обратите внимание, что в этом примере модель предварительного кодирования не подходит. Передний процесс должен иметь исключительно порт прослушивания, чтобы он мог проверять все входящие запросы до того, как они будут обработаны внутренним процессом.
Конечно, существует много типов приложений, в которых используется множество других моделей для управления состоянием. Выбор правильной модели для многопроцессорной обработки требует сначала понимания того, какой тип concurrency имеет смысл для вашего приложения и как вы можете управлять своим состоянием приложения.
С учетом того, что с очень новыми версиями Twisted (невыпущенными на данный момент), довольно легко разделить прослушивающий TCP-порт между несколькими процессами. Вот фрагмент кода, который демонстрирует один способ, которым вы можете использовать некоторые новые API для этого:
from os import environ
from sys import argv, executable
from socket import AF_INET
from twisted.internet import reactor
from twisted.web.server import Site
from twisted.web.static import File
def main(fd=None):
root = File("/var/www")
factory = Site(root)
if fd is None:
# Create a new listening port and several other processes to help out.
port = reactor.listenTCP(8080, factory)
for i in range(3):
reactor.spawnProcess(
None, executable, [executable, __file__, str(port.fileno())],
childFDs={0: 0, 1: 1, 2: 2, port.fileno(): port.fileno()},
env=environ)
else:
# Another process created the port, just start listening on it.
port = reactor.adoptStreamPort(fd, AF_INET, factory)
reactor.run()
if __name__ == '__main__':
if len(argv) == 1:
main()
else:
main(int(argv[1]))
С более старыми версиями вы иногда можете уйти с помощью fork
для совместного использования порта. Тем не менее, это скорее склонность к ошибкам, сбои на некоторых платформах и не поддерживаемый способ использования Twisted:
from os import fork
from twisted.internet import reactor
from twisted.web.server import Site
from twisted.web.static import File
def main():
root = File("/var/www")
factory = Site(root)
# Create a new listening port
port = reactor.listenTCP(8080, factory)
# Create a few more processes to also service that port
for i in range(3):
if fork() == 0:
# Proceed immediately onward in the children.
# The parent will continue the for loop.
break
reactor.run()
if __name__ == '__main__':
main()
Это работает из-за нормального поведения fork, где вновь созданный процесс (дочерний) наследует все дескрипторы памяти и файла из исходного процесса (родителя). Поскольку процессы в противном случае изолированы, эти два процесса не мешают друг другу, по крайней мере, до того, как выполняется код Python, который они выполняют. Поскольку дескрипторы файлов наследуются, родительский или любой из дочерних элементов могут принимать соединения на порту.
Поскольку перенаправление HTTP-запросов является такой простой задачей, я сомневаюсь, что вы заметите большую часть улучшения производительности, используя любой из этих методов. Первое немного лучше, чем проксирование, потому что оно упрощает развертывание и упрощает работу с приложениями, отличными от HTTP. Последнее, вероятно, скорее является обязательством, чем оно заслуживает.