19

在运行 TwistedWeb 服务器时,人们使用哪些技术来利用多个处理器/内核?有推荐的方法吗?

我的基于 twisted.web 的 Web 服务在 Amazon EC2 实例上运行,这些实例通常具有多个 CPU 内核(8、16),并且该服务所做的工作类型受益于额外的处理能力,所以我非常想使用那。

我知道可以在 Twisted 的多个实例之前使用配置为反向代理的 haproxy、squid 或 Web 服务器。事实上,我们目前正在使用这样的设置,nginx 充当多个上游twisted.web 服务的反向代理,这些服务在同一主机上运行,​​但每个服务都在不同的端口上。

这很好用,但我真正感兴趣的是一个没有“前置”服务器的解决方案,但所有扭曲的进程都以某种方式绑定到同一个套接字并接受请求。这样的事情甚至可能......还是我疯了?操作系统是 Linux (CentOS)。

谢谢。

安东。

4

3 回答 3

40

有多种方法可以支持 Twisted 应用程序的多进程操作。不过,一开始要回答的一个重要问题是,您希望并发模型是什么,以及您的应用程序如何处理共享状态。

在单进程 Twisted 应用程序中,并发都是协作的(在 Twisted 的异步 I/O API 的帮助下),共享状态可以保存在 Python 对象所在的任何地方。您的应用程序代码运行时知道,在它放弃控制之前,不会运行其他任何东西。此外,您的应用程序中想要访问某个共享状态的任何部分都可以很容易地做到这一点,因为该状态可能保存在一个易于访问的无聊的旧 Python 对象中。

当您有多个进程时,即使它们都在运行基于 Twisted 的应用程序,您也有两种形式的并发。一种与前一种情况相同——在特定进程中,并发是协作的。但是,您有一种新类型,其中正在运行多个进程。您平台的进程调度程序可能会随时在这些进程之间切换执行,而您对此几乎没有控制权(也几乎无法看到它何时发生)。它甚至可以安排您的两个进程在不同的内核上同时运行(这甚至可能是您所希望的)。这意味着您失去了一些关于一致性的保证,因为一个进程不知道第二个进程何时会出现并尝试在某些共享状态上进行操作。

与单进程模型不同,您不再有任何方便、易于访问的位置来存储您的所有代码都可以访问的状态。如果将它放在一个进程中,则该进程中的所有代码都可以像普通 Python 对象一样轻松访问它,但是在任何其他进程中运行的任何代码都无法再轻松访问它。您可能需要找到一个 RPC 系统来让您的进程相互通信。或者,您可以构建您的流程划分,以便每个流程只接收需要存储在该流程中的状态的请求。这方面的一个例子可能是一个带有会话的网站,其中有关用户的所有状态都存储在他们的会话中,并且他们的会话由 cookie 标识。前端进程可以接收 Web 请求,检查 cookie,查找负责该会话的后端进程,然后将请求转发到该后端进程。这种方案意味着后端通常不需要通信(只要您的 Web 应用程序足够简单 - 即,只要用户不相互交互,或对共享数据进行操作)。

请注意,在该示例中,预分叉模型是不合适的。前端进程必须独占监听端口,以便它可以在后端进程处理所有传入请求之前检查它们。

当然,有许多类型的应用程序,以及许多其他用于管理状态的模型。为多处理选择正确的模型需要首先了解哪种并发对您的应用程序有意义,以及如何管理应用程序的状态。

话虽如此,对于非常新版本的 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 应用程序。后者可能更多的是一种负担,而不是值得接受。

于 2012-04-10T12:13:02.360 回答
3

IMO 的推荐方式是haproxy像您一样使用(或其他负载均衡器),如果配置正确,瓶颈不应该是负载均衡器。此外,您还需要一些故障转移方法,haproxy以防您的某个进程出现故障。

不可能将多个进程绑定到同一个 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 作为生成子进程时的第二个参数。

完整的狙击手在这里:

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 回答