19

TwistedWeb サーバーを実行するときに複数のプロセッサ/コアを利用するために、人々はどのような手法を使用していますか? それを行うための推奨される方法はありますか?

私の twisted.web ベースの Web サービスは、多くの場合複数の CPU コア (8、16) を備えた Amazon EC2 インスタンスで実行されており、サービスが実行する作業の種類は追加の処理能力の恩恵を受けているので、ぜひ使用したいと思います。それ。

Twisted の複数のインスタンスの前で、リバース プロキシとして構成された haproxy、squid、または Web サーバーを使用できることを理解しています。実際、私たちは現在、nginx が同じホスト上で実行されているいくつかの上流の twisted.web サービスへのリバース プロキシとして機能するこのようなセットアップを使用していますが、それぞれが異なるポート上にあります。

これは問題なく動作しますが、私が本当に興味を持っているのは、「前面」サーバーがなく、すべての twistd プロセスが何らかの形で同じソケットにバインドされ、要求を受け入れるソリューションです。そのようなことは可能ですか... それとも私は狂っていますか? オペレーティング システムは Linux (CentOS) です。

ありがとう。

アントン。

4

3 に答える 3

40

ツイストアプリケーションのマルチプロセス操作をサポートする方法はいくつかあります。ただし、最初に答えるべき重要な質問の1つは、並行性モデルがどのようなものであると期待するか、およびアプリケーションが共有状態をどのように処理するかです。

単一プロセスのTwistedアプリケーションでは、同時実行性はすべて協調的であり(Twistedの非同期I / O APIの助けを借りて)、Pythonオブジェクトが移動する場所ならどこでも共有状態を維持できます。アプリケーションコードは、制御を放棄するまで、他に何も実行されないことを認識して実行されます。さらに、共有状態の一部にアクセスしたいアプリケーションのどの部分も、おそらく非常に簡単にアクセスできます。これは、その状態が、アクセスしやすい退屈な古いPythonオブジェクトに保持されているためです。

複数のプロセスがある場合、それらがすべてツイストベースのアプリケーションを実行している場合でも、2つの形式の同時実行性があります。1つは前のケースと同じです。特定のプロセス内では、並行性は協調的です。ただし、複数のプロセスが実行されている新しい種類があります。プラットフォームのプロセススケジューラは、これらのプロセス間でいつでも実行を切り替える可能性があり、これを制御することはほとんどできません(また、いつ発生するかについての可視性もほとんどありません)。2つのプロセスを異なるコアで同時に実行するようにスケジュールすることもできます(これはおそらくあなたが望んでいることです)。これは、1つのプロセスが、2番目のプロセスがいつやって来て、ある共有状態で動作しようとするかを知らないため、一貫性に関する保証が失われることを意味します。

単一プロセスモデルとは異なり、すべてのコードが到達できる状態を保存するための便利で簡単にアクセスできる場所はもうありません。1つのプロセスに入れると、そのプロセス内のすべてのコードは通常のPythonオブジェクトとして簡単にアクセスできますが、他のプロセスで実行されているコードは簡単にアクセスできなくなります。プロセスが相互に通信できるようにするために、RPCシステムを見つける必要がある場合があります。または、各プロセスがそのプロセスに格納された状態を必要とする要求のみを受信するように、プロセス分割を設計することもできます。この例としては、セッションのあるWebサイトがあります。このサイトでは、ユーザーに関するすべての状態がセッションに保存され、セッションはCookieによって識別されます。フロントエンドプロセスは、Webリクエストを受信し、Cookieを検査し、どのバックエンドプロセスがそのセッションを担当しているかを調べます。次に、リクエストをそのバックエンドプロセスに転送します。このスキームは、バックエンドが通常通信する必要がないことを意味します(Webアプリケーションが十分に単純である限り、つまり、ユーザーが相互に対話したり、共有データを操作したりしない限り)。

その例では、事前分岐モデルは適切ではないことに注意してください。フロントエンドプロセスは、バックエンドプロセスによって処理される前にすべての着信要求を検査できるように、リスニングポートを排他的に所有する必要があります。

もちろん、アプリケーションには多くの種類があり、状態を管理するための他の多くのモデルがあります。マルチプロセッシングに適したモデルを選択するには、まず、アプリケーションにとってどのような種類の同時実行が意味をなすのか、およびアプリケーションの状態をどのように管理できるのかを理解する必要があります。

そうは言っても、Twistedの非常に新しいバージョン(現時点ではリリースされていません)を使用すると、リスニングTCPポートを複数のプロセス間で共有するのは非常に簡単です。これを実現するためにいくつかの新しいAPIを使用する1つの方法を示すコードスニペットを次に示します。

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コードが実行される限り、2つのプロセスは互いに干渉しません。ファイル記述子は継承されるため、親または子のいずれかがポートで接続を受け入れることができます。

HTTPリクエストの転送は非常に簡単な作業であるため、これらの手法のいずれかを使用すると、パフォーマンスが大幅に向上することに気付くとは思えません。前者は、デプロイメントを簡素化し、非HTTPアプリケーションでより簡単に機能するため、プロキシよりも少し優れています。後者はおそらく、受け入れる価値があるよりも責任が大きいでしょう。

于 2012-04-10T12:13:02.360 に答える
3

IMOがhaproxy(または別のロードバランサーを)使用することをお勧めしますが、正しく構成されていれば、ボトルネックはロードバランサーではありません。haproxyさらに、プロセスの 1 つがダウンした場合に備えて、いくつかのフォールオーバー メソッドが必要になります。

複数のプロセスを同じ TCP ソケットにバインドすることはできませんが、UDP では可能です。

于 2012-04-09T18:35:01.283 に答える
1

HTTPS 経由でも Web コンテンツを提供したい場合は、@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親プロセスで両方を使用したい場合fdは、ssl ポートから取得した ssl を、子プロセスを生成するときに 2 番目の引数として含めます。

完全なスニッパーはここにあります:

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:]))
于 2014-01-21T09:56:29.733 に答える