【问题标题】:TwistedWeb on multicore/multiprocessor多核/多处理器上的 TwistedWeb
【发布时间】:2012-04-22 02:15:07
【问题描述】:

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

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

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

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

谢谢。

安东。

【问题讨论】:

    标签: twisted multiple-instances twisted.web


    【解决方案1】:

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

    不可能将多个进程绑定到同一个 TCP 套接字,但可以使用 UDP。

    【讨论】:

    • 伊恩,感谢您的回答。没错,负载均衡器不是瓶颈,我主要关心的是安全和缓冲区溢出类型的错误,这意味着如果我使用 nginx 或 haproxy,我需要跟踪漏洞并更新版本该软件定期。如果只有 Twisted.Web,那整个舞蹈就可以被绕开
    • 当时在想可能类似于 apache 对 pre-fork 所做的事情......
    • 类似于 Apache 所做的事情绝对是可能的。确实,套接字不能绑定到同一个地址,但是单个套接字绑定了某个地址可以共享​​>。另外,我不认为“推荐的方法是使用 haproxy”是一个很好的说法。我敢肯定有人会推荐它,但很多人会推荐别的东西。
    • 在 Python 的情况下,无论以何种方式对它进行切片,都需要为每个核心启动/分叉 1 个 Python 进程以充分利用资源。现在归结为如何将请求从套接字传递到不同的进程。负载均衡器易于设置,并通过请求队列、粘性属性和故障转移等附加功能提供出色的性能。我将不得不关注新的 Twisted 套接字共享功能,但 haproxy 是解决此问题的一种易于理解的方法。 @Orange:haproxy 从一开始就没有安全漏洞。
    • 实际上,在有限的情况下,多个进程可以绑定到一个 TCP 套接字。 SO_REUSEPORT 是 Linux 内核中一个相对较新的添加,它只允许这样做。见lwn.net/Articles/542629
    【解决方案2】:

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

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

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

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

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

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

    话虽如此,对于非常新版本的 Twisted(此时尚未发布),在多个进程之间共享侦听 TCP 端口非常容易。这是一段代码 sn-p,它演示了一种可以使用一些新 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 应用程序。后者可能更像是一种负担,而不是值得接受。

    【讨论】:

    • Jean-Paul,首先,感谢您的详细回答。非常感激!我的应用程序非常简单,进程之间根本没有共享状态,没有要处理的会话,进程之间不需要通信等。我的任务非常适合并行计算,而且(理论上)我真的不不在乎我的进程是否都在同一台服务器上运行,或者每个进程都在自己的机器上运行。我需要使用具有多核 CPU 的更大服务器的原因是亚马逊在这些服务器上提供了更好的 I/O 性能。
    • 浏览了一下 Twisted 主干源(posixbase.py、tcp.py),看起来重用现有套接字的更改确实很新。一定会密切关注这些新功能,并希望在 12.1 版本中看到它们:)
    • 我做了一些测试,有两个问题让我困惑:1)不仅工人,而且主人也会接受传入的连接 - 我怎样才能让只有工人接受? 2) 在reactor.listenTCP 调用上设置backlog 是否完全适用于套接字——即内核中的队列深度,因此适用于所有工作人员的组合?
    • 好的,找到答案并测试了 1):port.stopReading() 将禁用 master 上的接受连接。
    • 它不是——至少在迄今为止存在的任何 Twisted 版本中都不是。请注意adoptStreamPortIReactorSocket 的一部分,并且reactor 实现不会声明它实现IReactorSocket,除非socket.fromfd 存在。
    【解决方案3】:

    如果您也希望通过 HTTPS 提供您的网络内容,这就是您需要在 @Jean-Paul 的 sn-p 之上执行的操作。

    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,并在生成子进程时将您从 ssl 端口获得的 ssl fd 作为第二个参数包含在内。

    完整的狙击手在这里:

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

    【讨论】:

      猜你喜欢
      • 2015-12-29
      • 2020-02-26
      • 1970-01-01
      • 2020-09-05
      • 1970-01-01
      • 2014-12-17
      • 1970-01-01
      • 2021-11-09
      • 1970-01-01
      相关资源
      最近更新 更多