18

Вопрос по – TwistedWeb на многоядерных / многопроцессорных

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

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

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

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

Благодарю.

Антон.

3ответа

3

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

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

40

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

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

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

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

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

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

При этом с очень новыми версиями 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. Последнее, вероятно, является более ответственным, чем его стоит принять.

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 на родительском процессе и включают SSLfd вы получаете от порта ssl в качестве второго аргумента при порождении дочернего процесса.

Полный снайпер здесь:

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:]))

RelatedQuestions