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

18

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

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

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

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

Благодарю.

Антон.

Ваш Ответ

3   ответа
1

это то, что вам нужно сделать поверх фрагмента @ 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:]))
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. Последнее, вероятно, является более ответственным, чем его стоит принять.

немного просмотрел источник Twisted trunk (posixbase.py, tcp.py), и похоже, что изменения для повторного использования ранее существующего сокета действительно довольно новы. Обязательно буду следить за этими новыми функциями и надеемся увидеть их в 12.1 выпуске :) Orange Juce
Это не так - по крайней мере, ни в одной версии Twisted, которая существует до сих пор. Обратите внимание, чтоadoptStreamPort это частьIReactorSocket и реализация реактора не заявляет, что он реализуетIReactorSocket еслиsocket.fromfd существует.
Жан-Поль, прежде всего, спасибо за этот подробный ответ. Очень признателен! Мое приложение довольно простое: нет общего состояния между процессами, нет сеансов для обработки, нет необходимости в обмене данными между процессами и т. Д. Моя задача хорошо подходит для параллельных вычислений, и (в теории) я действительно не знаю. Мне все равно, все ли процессы выполняются на одном и том же сервере или каждый процесс работает на своей машине. Причина, по которой мне нужно использовать большие серверы с многоядерными процессорами, заключается в том, что Amazon предлагает более высокую производительность ввода-вывода для них. Orange Juce
Хорошо, нашёл ответ и проверил на 1): port.stopReading () отключит прием соединений на master.
Я провел некоторое тестирование, и меня озадачивают два вопроса: 1) не только работники, но и мастер будут принимать входящие соединения - как я могу сделать так, чтобы только работники принимали? 2) правильно ли этот параметрbacklog наreactor.listenTCP вызов будет применяться к сокету в целом - то есть глубина очереди в ядре, и, следовательно, для всех работников вместе взятых?
3

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

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

На самом деле в ограниченных случаях несколько процессов могут связываться с сокетом TCP. SO_REUSEPORT - сравнительно недавнее дополнение к ядру Linux, которое позволяет именно это. Увидетьlwn.net/Articles/542629
Нечто подобное тому, что делает Apache, определенно возможно. Это правда, что сокеты не могут бытьbound к тому же адресу, но один сокет ограничен каким-то адресомcan бытьshared, Кроме того, я не думаю, что рекомендуемый способ - использовать haproxy. это очень хорошее утверждение. Я уверен, что кто-то порекомендует это, но множество людей порекомендуют что-то еще.
Йен, спасибо за твой ответ. Вы правы, балансировщик нагрузки не является узким местом, моей главной заботой были в основном ошибки безопасности и ошибки типа переполнения буфера, что означает, что если я использую nginx или haproxy, мне нужно отслеживать уязвимости и обновлять версию это программное обеспечение регулярно. Весь этот танец можно было бы обойти, если бы был только Twisted.Web Orange Juce
В случае с Python при любом его разрезании вам потребуется запустить / разветвить 1 процесс Python на ядро, чтобы полностью использовать ресурсы. Теперь все сводится к тому, как передавать запросы из сокета в разные процессы. Балансировщик нагрузки прост в настройке и обеспечивает отличную производительность благодаря дополнительным функциям, таким как организация очереди запросов, закрепление атрибутов и отказоустойчивость. Мне придется следить за новыми возможностями совместного использования Twisted сокетов, но haproxy - это хорошо понятный способ решения этой проблемы. @Orange: у haproxy никогда не было уязвимости безопасности с момента его запуска.
думал, что возможно что-то похожее на то, что apache делает с pre-fork ... Orange Juce

Похожие вопросы