stringtranslate.com

Patrón de reactor

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]

Descripción general

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:

  1. Mantenga un controlador de eventos de un solo subproceso; los subprocesos múltiples introducen sobrecarga y complejidad sin resolver el problema real del bloqueo de E/S
  2. Utilice un mecanismo de notificación de eventos para demultiplexar solicitudes solo después de que se complete la E/S (de modo que la E/S sea efectivamente no bloqueante)
  3. Registre los controladores de solicitudes como devoluciones de llamadas con el controlador de eventos para una mejor separación de preocupaciones

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]

Uso

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]

Aplicaciones

El patrón reactor (o una variante del mismo) ha encontrado un lugar en muchos servidores web, servidores de aplicaciones y marcos de redes:

Estructura

Una aplicación reactiva consta de varias partes móviles y dependerá de algunos mecanismos de soporte: [1]

Manejar
Un identificador e interfaz para una solicitud específica, con E/S y datos. Esto suele adoptar la forma de un socket, descriptor de archivo o mecanismo similar, que debería proporcionar la mayoría de los sistemas operativos modernos.
Demultiplexor
Un notificador de eventos que puede monitorear de manera eficiente el estado de un identificador y luego notificar a otros subsistemas sobre un cambio de estado relevante (normalmente, un identificador de E/S que se vuelve "listo para leer"). Tradicionalmente, esta función la cumplía la llamada al sistema select() , pero algunos ejemplos más contemporáneos incluyen epoll , kqueue e IOCP .
Transportista
El bucle de eventos real de la aplicación reactiva, este componente mantiene el registro de controladores de eventos válidos y luego invoca el controlador apropiado cuando se genera un evento.
Controlador de eventos
También conocido como controlador de solicitudes, es la lógica específica para procesar un tipo de solicitud de servicio. El patrón de reactor sugiere registrarlas dinámicamente con el despachador como devoluciones de llamadas para una mayor flexibilidad. De manera predeterminada, un reactor no utiliza subprocesos múltiples, sino que invoca un controlador de solicitudes dentro del mismo subproceso que el despachador.
Interfaz del controlador de eventos
Una clase de interfaz abstracta que representa las propiedades y métodos generales de un controlador de eventos. Cada controlador específico debe implementar esta interfaz, mientras que el despachador operará en los controladores de eventos a través de esta interfaz.

Variantes

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]

Véase también

Patrones relacionados:

Notas

  1. ^ Dicho esto, una regla general en el diseño de software es que si las demandas de la aplicación pueden aumentar potencialmente más allá de un límite supuesto, uno debe esperar que algún día lo hagan.

Referencias

  1. ^ abcdefghijk Schmidt, Douglas C. (1995). "Capítulo 29: Reactor: un patrón de comportamiento de objetos para demultiplexar y enviar identificadores para eventos sincrónicos" (PDF) . En Coplien, James O. (ed.). Lenguajes de patrones de diseño de programas . Vol. 1 (1.ª ed.). Addison-Wesley. ISBN 9780201607345.
  2. ^ abcdefg Devresse, Adrien (20 de junio de 2014). «E/S paralelas eficientes en arquitecturas multinúcleo» (PDF) . 2.ª Escuela Temática de Computación del CERN . CERN. Archivado (PDF) del original el 8 de agosto de 2022. Consultado el 14 de septiembre de 2023 .
  3. ^ abcde Escoffier, Clement; Finnegan, Ken (noviembre de 2021). "Capítulo 4. Principios de diseño de sistemas reactivos" . Sistemas reactivos en Java . O'Reilly Media. ISBN 9781492091721.
  4. ^ abc Garrett, Owen (10 de junio de 2015). «Inside NGINX: How We Designed for Performance & Scale» (Dentro de NGINX: cómo diseñamos para el rendimiento y la escalabilidad). NGINX . F5, Inc. Archivado desde el original el 20 de agosto de 2023. Consultado el 10 de septiembre de 2023 .
  5. ^ Kegel, Dan (5 de febrero de 2014). "El problema del C10k". Dan Kegel's Web Hostel . Archivado desde el original el 6 de septiembre de 2023. Consultado el 10 de septiembre de 2023 .
  6. ^ Bonér, Jonas (15 de junio de 2022). «Los patrones reactivos: 3. Aislar mutaciones». Los principios reactivos . Consultado el 20 de septiembre de 2023 .
  7. ^ "Programación de redes: escritura de aplicaciones de red e Internet" (PDF) . Proyecto POCO . Applied Informatics Software Engineering GmbH. 2010. págs. 21–22 . Consultado el 20 de septiembre de 2023 .
  8. ^ Stoyanchev, Rossen (9 de febrero de 2016). «Reactive Spring». Spring.io . Consultado el 20 de septiembre de 2023 .
  9. ^ "Descripción general del reactor". twisted.org . Consultado el 28 de julio de 2024 .

Enlaces externos

Aplicaciones específicas:

Implementaciones de muestra: