2012-04-09 6 views
17

Какие методы используются для использования нескольких процессоров/ядер при запуске сервера TwistedWeb? Есть ли рекомендуемый способ сделать это?TwistedWeb на многоядерном/многопроцессорном компьютере

Моя веб-служба на основе twisted.web работает на экземплярах Amazon EC2, которые часто имеют несколько ядер процессора (8, 16), а также тип работы, выполняемой службой, благодаря дополнительной вычислительной мощности, поэтому я бы очень очень нравится использовать это.

Я понимаю, что можно использовать haproxy, squid или веб-сервер, настроенный как обратный прокси, перед несколькими экземплярами Twisted. Фактически, мы в настоящее время используем такую ​​настройку, причем nginx служит в качестве обратного прокси-сервера нескольким восходящим службам twisted.web, работающим на одном и том же хосте, но каждый на другом порту.

Это прекрасно работает, но то, что меня действительно интересует, является решением, в котором нет «фронтального» сервера, но все процессы twistd каким-то образом привязаны к одному и тому же сокету и принимают запросы. Возможно ли это даже ... или я сумасшедший? Операционная система - Linux (CentOS).

Спасибо.

Антон.

ответ

39

Существует множество способов поддержки многопроцессорной работы для витой программы. Однако одним из важных вопросов для ответа на этот вопрос является то, что вы ожидаете от своей модели параллелизма, и как ваше приложение имеет дело с общим состоянием.

В одном процессе Скрученное приложение, параллелизм все взаимодействует (с помощью асинхронных API ввода-вывода Twisted), а общее состояние может храниться в любом месте объекта Python. Ваш код приложения работает, зная, что до тех пор, пока он не откажется от контроля, больше ничего не будет запущено. Кроме того, любая часть вашего приложения, которая хочет получить доступ к некоторой части общего состояния, может, вероятно, сделать это довольно легко, поскольку это состояние, вероятно, хранится в скучном старом объекте Python, к которому легко получить доступ.

Если у вас есть несколько процессов, даже если они все запущены приложениями с витой поддержкой, то у вас есть две формы параллелизма. Один из них аналогичен предыдущему случаю - в рамках конкретного процесса параллелизм является совместным. Тем не менее, у вас новый вид, в котором работают несколько процессов. Планировщик процессов вашей платформы может переключать выполнение между этими процессами в любое время, и у вас очень мало контроля над этим (а также очень мало видимости того, когда это произойдет). Он может даже планировать два из ваших процессов одновременно работать на разных ядрах (возможно, это даже то, на что вы надеетесь). Это означает, что вы теряете некоторые гарантии относительно согласованности, поскольку один процесс не знает, когда второй процесс может прийти и попытаться работать в каком-то общем состоянии. Это приводит к другой важной области рассмотрения, как вы фактически разделяете состояние между процессами.

В отличие от единой модели процесса, у вас больше нет удобных и легкодоступных мест для хранения вашего состояния, где весь ваш код может достичь этого. Если вы поместите его в один процесс, весь код в этом процессе может легко получить доступ к нему как к обычным объектам Python, но любой код, запущенный в любом из ваших других процессов, больше не имеет легкого доступа к нему. Возможно, вам придется найти систему RPC, чтобы ваши процессы взаимодействовали друг с другом.Или вы можете архивировать свой процесс, чтобы каждый процесс получал запросы, которые требуют сохранения состояния в этом процессе. Примером этого может быть веб-сайт с сеансами, где все состояние о пользователе хранится в их сеансе, а их сеансы идентифицируются с помощью файлов cookie. Интерфейсный процесс может получать веб-запросы, проверять куки-файлы, искать, какой серверный процесс отвечает за этот сеанс, а затем перенаправить запрос на этот внутренний процесс. Эта схема означает, что back-end обычно не требуется связываться (пока ваше веб-приложение достаточно простое, то есть до тех пор, пока пользователи не будут взаимодействовать друг с другом или работают с общими данными).

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

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

С учетом того, что с очень новыми версиями 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() 

Это работает из-за нормальное поведение вилки, где вновь созданный процесс (ребенок) наследует все дескрипторы памяти и файла из исходного процесса (родителя). Поскольку процессы в противном случае изолированы, эти два процесса не мешают друг другу, по крайней мере, до того, как выполняется код Python, который они выполняют. Поскольку файловые дескрипторы унаследованы, родительский или любой из дочерних объектов может принимать соединения в порту.

Поскольку перенаправление HTTP-запросов - такая легкая задача, я сомневаюсь, что вы заметите большую часть улучшения производительности, используя любой из этих методов. Первое немного лучше, чем проксирование, потому что оно упрощает развертывание и упрощает работу с приложениями, отличными от HTTP. Последнее, вероятно, является большей частью ответственности, чем того стоит.

+0

Jean-Paul, прежде всего, спасибо за этот подробный ответ. Очень признателен! Мое приложение довольно просто, между процессами вообще нет общего состояния, нет сеансов для обработки, нет необходимости в общении между процессами и т. Д. Моя задача хорошо подходит для параллельных вычислений и (теоретически) не заботьтесь, чтобы мои процессы выполнялись на одном сервере или каждый процесс запускался на его собственной машине. Причина, по которой мне нужно использовать более крупные серверы с многоядерными процессорами, - это то, что Amazon предлагает лучшую производительность ввода-вывода. –

+0

немного переработал источник Twisted trunk (posixbase.py, tcp.py), и похоже, что изменения в повторном использовании существующего сокета действительно очень новые. Обязательно буду следить за этими новыми функциями и надеяться увидеть их в выпуске 12.1 :) –

+0

Я провел несколько тестов, и у меня есть два вопроса: 1) не только рабочие, но и мастер принимают входящие соединения - как я могу заставить только рабочих принять? 2) верно ли, что установка «backlog» на вызов «reactor.listenTCP» будет применяться к сокету в целом - это глубина очереди в ядре и, следовательно, для всех рабочих вместе? – oberstet

3

Рекомендуемый способ: IMO должен использовать haproxy (или другой балансировщик нагрузки), как и вы, узким местом не должно быть балансировщик нагрузки, если он настроен правильно. Кроме того, вы захотите использовать какой-либо метод переполнения, который haproxy обеспечивает в случае, если один из ваших процессов снизится.

Невозможно связать несколько процессов с одним и тем же TCP-сокетом, но это возможно с помощью UDP.

+0

Ян, спасибо за Ваш ответ. Вы правы, балансировщик нагрузки не является узким местом, моя главная проблема заключалась в основном в проблемах с защитой и переполнением буфера, что означает, что если я иду с nginx или haproxy, мне нужно отслеживать уязвимости и обновлять версию это программное обеспечение регулярно. Весь этот танец мог быть отшатнут, если бы только Twisted.Web –

+0

думал, может быть, что-то похожее на то, что apache делает с pre-fork, было бы возможно ... –

+3

Нечто похожее на то, что делает Apache, определенно возможно. Это правда, что сокеты не могут быть * связаны * с одним и тем же адресом, но один сокет привязывает некоторый адрес * может * быть * общим *. Кроме того, я не думаю, что «Рекомендуемый способ использования haproxy» - очень хорошее утверждение. Я уверен, кто-то порекомендовал бы это, но многие люди порекомендуют что-то еще. –

1

Если вы хотите обслуживать свой веб-контент через HTTPS, это то, что вам нужно сделать в дополнение к фрагменту @ Jean-Paul.

from twisted.internet.ssl import PrivateCertificate 
from twisted.protocols.tls import TLSMemoryBIOFactory 

''' 
Original snippet goes here 
.......... 
............... 
''' 

privateCert = PrivateCertificate.loadPEM(open('./server.cer').read() + open('./server.key').read()) 
tlsFactory = TLSMemoryBIOFactory(privateCert.options(), False, factory) 
reactor.adoptStreamPort(fd, AF_INET, tlsFactory) 

С помощью fd, вы будете служить либо HTTP или HTTPS, но не оба. Если вы хотите иметь оба, listenSSL на родительском процессе и включить ssl fd, вы получаете из порта ssl в качестве второго аргумента при нерестах дочернего процесса.

Полная Snipper здесь:

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 

from twisted.internet import reactor, ssl 
from twisted.internet.ssl import PrivateCertificate 
from twisted.protocols.tls import TLSMemoryBIOFactory 

def main(fd=None, fd_ssl=None): 
    root = File("/var/www") 
    factory = Site(root) 

    spawned = [] 
    if fd is None: 
     # Create a new listening port and several other processes to help out.                  
     port = reactor.listenTCP(8080, factory) 
     port_ssl = reactor.listenSSL(8443, factory, ssl.DefaultOpenSSLContextFactory('./server.key', './server.cer')) 
     for i in range(3): 
      child = reactor.spawnProcess(
       None, executable, [executable, __file__, str(port.fileno()), str(port_ssl.fileno())], 
       childFDs={0: 0, 1: 1, 2: 2, port.fileno(): port.fileno(), port_ssl.fileno(): port_ssl.fileno()}, 
       env=environ) 
      spawned.append(child) 
    else: 
     # Another process created the port, just start listening on it.                    
     port = reactor.adoptStreamPort(fd, AF_INET, factory) 
     cer = open('./server.cer') 
     key = open('./server.key') 
     pem_data = cer.read() + key.read() 
     cer.close() 
     pem.close() 
     privateCert = PrivateCertificate.loadPEM(pem_data) 
     tlsFactory = TLSMemoryBIOFactory(privateCert.options(), False, factory) 
     reactor.adoptStreamPort(fd_ssl, AF_INET, tlsFactory) 

    reactor.run() 

    for p in spawned: 
     p.signalProcess('INT') 


if __name__ == '__main__': 
    if len(argv) == 1: 
     main() 
    else: 
     main(int(argv[1:])) 
Смежные вопросы