运行TwistedWeb服务器时,人们使用哪些技术来使用多个处理器/核心?有推荐的方法吗?
我的基于twisted.web的Web服务在Amazon EC2实例上运行,该实例通常具有多个CPU核心(8,16),并且该服务所做的工作类型受益于额外的处理能力,所以我非常喜欢喜欢用它。
据我所知,可以在Twisted的多个实例前面使用haproxy,squid或配置为反向代理的Web服务器。实际上,我们目前正在使用这样的设置,nginx用作在同一主机上运行的几个上游twisted.web服务的反向代理,但每个服务在不同的端口上。
这很好,但我真正感兴趣的是,没有“前置”服务器的解决方案,但所有扭曲的进程以某种方式绑定到同一个套接字并接受请求。这样的事情甚至可能......还是我疯了?操作系统是Linux(CentOS)。
感谢。
安东。
答案 0 :(得分: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应用程序。后者可能更值得接受,而不是值得接受。
答案 1 :(得分:3)
IMO建议的方式是使用haproxy
(或其他负载均衡器),如果配置正确,则瓶颈不应该是负载均衡器。此外,你会想要一些haproxy
提供的失误转移方法,以防你的一个进程失败。
无法将多个进程绑定到同一个TCP套接字,但可以使用UDP。
答案 2 :(得分:1)
如果您希望通过HTTPS提供网络内容,那么您需要在@ 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
,并在生成子进程时将ssl fd
作为第二个参数包含在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:]))