多核/多处理器上的TwistedWeb

Ora*_*uce 19 twisted multiple-instances twisted.web

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

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

据我所知,可以在多个Twisted实例的前面使用haproxy,squid或配置为反向代理的Web服务器.实际上,我们目前正在使用这样的设置,nginx用作在同一主机上运行的几个上游twisted.web服务的反向代理,但每个服务在不同的端口上.

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

谢谢.

安东.

Jea*_*one 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]))
Run Code Online (Sandbox Code Playgroud)

对于旧版本,您有时可以使用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()
Run Code Online (Sandbox Code Playgroud)

这是因为fork的正常行为,其中新创建的进程(子进程)从原始进程(父进程)继承所有内存和文件描述符.由于进程是以其他方式隔离的,因此这两个进程不会相互干扰,至少就它们正在执行的Python代码而言.由于文件描述符是继承的,因此父项或任何子项都可以接受端口上的连接.

由于转发HTTP请求是一项非常容易的任务,因此我怀疑使用这些技术中的任何一种都会发现性能提升很多.前者比代理更好,因为它简化了部署,更容易适用于非HTTP应用程序.后者可能更值得接受,而不是值得接受.