ventajas sirven que procesadores procesador para nucleos multiprocesador multinucleo mononucleo microprocesador los historia entre diferencia desventajas caracteristicas arquitectura twisted multiple-instances twisted.web

sirven - TwistedWeb en multinĂșcleo/multiprocesador



procesador multinucleo ventajas y desventajas (3)

¿Qué técnicas utilizan las personas para utilizar múltiples procesadores / núcleos al ejecutar un servidor TwistedWeb? ¿Hay alguna forma recomendada de hacerlo?

Mi servicio web basado en twisted.web se ejecuta en instancias de Amazon EC2, que a menudo tienen múltiples núcleos de CPU (8, 16), y el tipo de trabajo que el servicio está haciendo se beneficia de la capacidad de procesamiento adicional, por lo que me gustaría mucho utilizar ese.

Entiendo que es posible utilizar haproxy, squid o un servidor web, configurado como un proxy inverso, frente a varias instancias de Twisted. De hecho, actualmente estamos usando una configuración de este tipo, con nginx como proxy inverso para varios servicios twisted.web ascendentes que se ejecutan en el mismo host, pero cada uno en un puerto diferente.

Esto funciona bien, pero en lo que estoy realmente interesado es en una solución donde no hay un servidor "frontal", pero todos los procesos twistd se vinculan de alguna manera al mismo socket y aceptan las solicitudes. ¿Es tal cosa posible ... o estoy loca? El sistema operativo es Linux (CentOS).

Gracias.

Anton


Hay varias formas de admitir la operación de multiprocesamiento para una aplicación Twisted. Sin embargo, una pregunta importante que debe responder al comienzo es cuál espera que sea su modelo de concurrencia y cómo su aplicación trata el estado compartido.

En una aplicación Twisted de un solo proceso, la concurrencia es totalmente cooperativa (con la ayuda de las API de E / S asíncronas de Twisted) y el estado compartido se puede mantener en cualquier lugar donde un objeto Python iría. El código de la aplicación se ejecuta sabiendo que, hasta que ceda el control, no se ejecutará nada más. Además, cualquier parte de la aplicación que quiera acceder a una parte del estado compartido probablemente pueda hacerlo con bastante facilidad, ya que ese estado probablemente se mantenga en un viejo y aburrido objeto de Python al que es fácil acceder.

Cuando tiene múltiples procesos, incluso si todos ellos ejecutan aplicaciones basadas en Twisted, entonces tiene dos formas de concurrencia. Una es la misma que en el caso anterior: dentro de un proceso particular, la concurrencia es cooperativa. Sin embargo, tiene un nuevo tipo, donde se ejecutan múltiples procesos. El programador de procesos de su plataforma puede cambiar la ejecución entre estos procesos en cualquier momento, y usted tiene muy poco control sobre esto (así como muy poca visibilidad de cuándo sucede). Incluso podría programar dos de sus procesos para que se ejecuten simultáneamente en diferentes núcleos (esto es probablemente lo que está esperando). Esto significa que se pierden algunas garantías sobre la consistencia, ya que un proceso no sabe cuándo puede ocurrir un segundo proceso e intenta operar en algún estado compartido. Esto conduce a la otra área importante de consideración, cómo realmente compartirá el estado entre los procesos.

A diferencia del modelo de proceso único, ya no tiene lugares convenientes y de fácil acceso para almacenar su estado donde todo su código pueda alcanzarlo. Si lo pone en un solo proceso, todo el código de ese proceso puede acceder a él fácilmente como un objeto de Python normal, pero cualquier código que se ejecute en cualquiera de sus otros procesos ya no tendrá acceso a él fácilmente. Es posible que deba encontrar un sistema RPC para permitir que sus procesos se comuniquen entre sí. O bien, puede diseñar la división de su proceso para que cada proceso solo reciba solicitudes que requieran un estado almacenado en ese proceso. Un ejemplo de esto podría ser un sitio web con sesiones, donde todos los estados sobre un usuario se almacenan en su sesión y sus sesiones se identifican mediante cookies. Un proceso de front-end podría recibir solicitudes web, inspeccionar la cookie, buscar qué proceso de back-end es responsable de esa sesión y luego enviar la solicitud a ese proceso de back-end. Este esquema significa que, por lo general, los back-ends no necesitan comunicarse (siempre que su aplicación web sea lo suficientemente simple, es decir, siempre que los usuarios no interactúen entre sí o operen con datos compartidos).

Tenga en cuenta que en ese ejemplo, un modelo de pre-forking no es apropiado. El proceso de front-end debe poseer exclusivamente el puerto de escucha para que pueda inspeccionar todas las solicitudes entrantes antes de que sean manejadas por un proceso de back-end.

Por supuesto, hay muchos tipos de aplicaciones, con muchos otros modelos para administrar el estado. La selección del modelo correcto para el multiprocesamiento requiere primero comprender qué tipo de concurrencia tiene sentido para su aplicación y cómo puede administrar el estado de su aplicación.

Dicho esto, con versiones muy nuevas de Twisted (no publicado hasta este momento), es bastante fácil compartir un puerto TCP que escucha entre múltiples procesos. Aquí hay un fragmento de código que demuestra una forma en la que podrías usar algunas API nuevas para lograr esto:

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

Con versiones anteriores, a veces puede salirse con el uso de fork para compartir el puerto. Sin embargo, esto es más bien propenso a errores, falla en algunas plataformas y no es una forma compatible de usar 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()

Esto funciona debido al comportamiento normal de la bifurcación, donde el proceso recién creado (el hijo) hereda toda la memoria y los descriptores de archivo del proceso original (el padre). Como los procesos están aislados, los dos procesos no interfieren entre sí, al menos en lo que respecta al código Python que están ejecutando. Dado que los descriptores de archivo se heredan, el padre o cualquiera de los hijos puede aceptar conexiones en el puerto.

Dado que el reenvío de solicitudes HTTP es una tarea tan fácil, dudo que note una mejora en el rendimiento al usar cualquiera de estas técnicas. El primero es un poco más agradable que el proxy, porque simplifica su implementación y funciona con más facilidad para aplicaciones no HTTP. Este último es probablemente más una responsabilidad que vale la pena aceptar.


La forma recomendada de IMO es utilizar haproxy (u otro equilibrador de carga) como lo está haciendo usted, el cuello de botella no debería ser el equilibrador de carga si está configurado correctamente. Además, querrá tener algún método fallido que haproxy proporcione en caso de que uno de sus procesos falle.

No es posible vincular varios procesos al mismo socket TCP, pero es posible con UDP.


Si también desea servir su contenido web a través de HTTPS, esto es lo que tendrá que hacer en la parte superior del fragmento de código de @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)

Al utilizar fd , servirá HTTP o HTTPS pero no ambos. Si desea tener ambos, listenSSL en el proceso principal e incluya el fd ssl que obtiene del puerto ssl como el segundo argumento al generar el proceso secundario.

Snipper completo está aquí:

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