El patrón de diseño de software Reactor es una estrategia de manejo de eventos que puede responder a muchas solicitudes de servicio potenciales de manera simultánea . El componente clave del patrón es un bucle de eventos , que se ejecuta en un solo hilo o proceso , que demultiplexa las solicitudes entrantes y las envía al controlador de solicitudes correcto. [1]
Al confiar en mecanismos basados en eventos en lugar de bloquear la E/S o el uso de múltiples subprocesos, un reactor puede manejar muchas solicitudes simultáneas vinculadas a E/S con un retraso mínimo. [2] Un reactor también permite modificar o expandir fácilmente rutinas de manejo de solicitudes específicas, aunque el patrón tiene algunas desventajas y limitaciones. [1]
Con su equilibrio entre simplicidad y escalabilidad , el reactor se ha convertido en un elemento arquitectónico central en varias aplicaciones de servidor y marcos de software para redes . También existen derivaciones como el multireactor y el proactor para casos especiales en los que se necesita un mayor rendimiento, capacidad de procesamiento o complejidad de las solicitudes. [1] [2] [3] [4]
Las consideraciones prácticas para el modelo cliente-servidor en redes grandes, como el problema C10k para servidores web , fueron la motivación original para el patrón reactor. [5]
Un enfoque ingenuo para manejar solicitudes de servicio desde muchos puntos finales potenciales, como sockets de red o descriptores de archivos , es escuchar nuevas solicitudes desde dentro de un bucle de eventos y luego leer inmediatamente la solicitud más antigua. Una vez que se ha leído toda la solicitud, se puede procesar y reenviar llamando directamente al controlador apropiado. Un servidor completamente "iterativo" como este, que maneja una solicitud de principio a fin por iteración del bucle de eventos, es lógicamente válido. Sin embargo, se quedará atrás una vez que reciba múltiples solicitudes en rápida sucesión. El enfoque iterativo no puede escalar porque la lectura de la solicitud bloquea el único hilo del servidor hasta que se recibe la solicitud completa, y las operaciones de E/S suelen ser mucho más lentas que otros cálculos. [2]
Una estrategia para superar esta limitación es el uso de múltiples subprocesos: al dividir inmediatamente cada nueva solicitud en su propio subproceso de trabajo, la primera solicitud ya no bloqueará el bucle de eventos, que puede iterar y manejar inmediatamente otra solicitud. Este diseño de "subproceso por conexión" escala mejor que uno puramente iterativo, pero aún contiene múltiples ineficiencias y tendrá dificultades más allá de un punto. Desde el punto de vista de los recursos subyacentes del sistema , cada nuevo subproceso o proceso impone costos generales en memoria y tiempo de procesamiento (debido al cambio de contexto ). La ineficiencia fundamental de cada subproceso que espera a que finalice la E/S tampoco se resuelve. [1] [2]
Desde el punto de vista del diseño, ambos enfoques acoplan estrechamente el demultiplexor general con controladores de solicitudes específicos, lo que hace que el código del servidor sea frágil y tedioso de modificar. Estas consideraciones sugieren algunas decisiones de diseño importantes:
La combinación de estos conocimientos da como resultado el patrón de reactor, que equilibra las ventajas del subproceso único con un alto rendimiento y escalabilidad. [1] [2]
El patrón de reactor puede ser un buen punto de partida para cualquier problema de manejo de eventos concurrentes. El patrón no se limita a los sockets de red; la E/S de hardware, el acceso al sistema de archivos o a la base de datos , la comunicación entre procesos e incluso los sistemas de paso de mensajes abstractos son todos casos de uso posibles. [ cita requerida ]
Sin embargo, el patrón de reactor tiene limitaciones, una de las principales es el uso de devoluciones de llamadas, que dificultan el análisis y la depuración del programa , un problema común en los diseños con control invertido . [1] Los enfoques más simples de subproceso por conexión y completamente iterativos evitan esto y pueden ser soluciones válidas si no se requiere escalabilidad o alto rendimiento. [a] [ cita requerida ]
El uso de un solo subproceso también puede convertirse en un inconveniente en los casos de uso que requieren un rendimiento máximo o cuando las solicitudes implican un procesamiento significativo. Diferentes diseños de subprocesos múltiples pueden superar estas limitaciones y, de hecho, algunos aún utilizan el patrón de reactor como un subcomponente para gestionar eventos y E/S. [1]
El patrón reactor (o una variante del mismo) ha encontrado un lugar en muchos servidores web, servidores de aplicaciones y marcos de redes:
Una aplicación reactiva consta de varias partes móviles y dependerá de algunos mecanismos de soporte: [1]
El patrón de reactor estándar es suficiente para muchas aplicaciones, pero para aquellas particularmente exigentes, los ajustes pueden proporcionar incluso más potencia al precio de una complejidad adicional.
Una modificación básica es invocar controladores de eventos en sus propios subprocesos para lograr una mayor concurrencia. Ejecutar los controladores en un grupo de subprocesos , en lugar de crear nuevos subprocesos según sea necesario, simplificará aún más el procesamiento multihilo y minimizará la sobrecarga. Esto hace que el grupo de subprocesos sea un complemento natural para el patrón de reactor en muchos casos de uso. [2]
Otra forma de maximizar el rendimiento es reintroducir parcialmente el enfoque del servidor de "subproceso por conexión", con despachadores replicados/bucles de eventos ejecutándose simultáneamente. Sin embargo, en lugar de la cantidad de conexiones, se configura la cantidad de despachadores para que coincida con los núcleos de CPU disponibles del hardware subyacente.
Esta variante, conocida como multireactor, garantiza que un servidor dedicado utilice por completo la potencia de procesamiento del hardware. Debido a que los subprocesos diferenciados son bucles de eventos de larga duración, la sobrecarga de crear y destruir subprocesos se limita al inicio y apagado del servidor. Con solicitudes distribuidas entre despachadores independientes, un multireactor también proporciona una mejor disponibilidad y robustez; si ocurre un error y falla un solo despachador, solo interrumpirá las solicitudes asignadas a ese bucle de eventos. [3] [4]
Para servicios particularmente complejos, donde se deben combinar demandas sincrónicas y asincrónicas, otra alternativa es el patrón proactor. Este patrón es más intrincado que un reactor, con sus propios detalles de ingeniería, pero aún hace uso de un subcomponente reactor para resolver el problema del bloqueo de IO. [3]
Patrones relacionados:
Aplicaciones específicas:
Implementaciones de muestra: