Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Coordine un conjunto de acciones distribuidas como una única operación. Si ocurre un fallo en cualquiera de las acciones, intente gestionar de forma transparente los errores o deshaga el trabajo realizado, para que la operación tenga éxito o falle por completo en conjunto. De este modo se agrega resistencia al sistema distribuido y se permite que se recupere y que repita las acciones que produjeron errores duraderos, de proceso o a causa de excepciones transitorias.
Contexto y problema
Una aplicación realiza tareas que incluyen varios pasos, algunos de los cuales pueden invocar servicios remotos o acceder a recursos remotos. Los pasos individuales podrían ser independientes entre sí, pero están organizados por la lógica de la aplicación que implementa la tarea.
Siempre que sea posible, la aplicación debe asegurarse de que la tarea se ejecuta por completo y resuelve los errores que puedan producirse al acceder a servicios o recursos remotos. Pueden producirse errores por diversos motivos. Por ejemplo, la red podría estar inactiva o las comunicaciones, interrumpidas; un servicio remoto podría no responder o encontrarse en estado inestable; o un recurso remoto podría estar inaccesible temporalmente, quizás por restricciones. En muchos casos, los errores serán transitorios y se podrán controlar mediante el patrón Retry.
Si la aplicación detecta un error más permanente o que no se puede recuperar fácilmente, debe ser capaz de restaurar el sistema a un estado coherente y garantizar la integridad de toda la operación.
Solución
El patrón Scheduler Agent Supervisor define los actores siguientes. Estos actores orquestan los pasos necesarios que se deben incluir como parte de la tarea global.
El componente Scheduler organiza la ejecución de los pasos que forman la tarea y orquesta su funcionamiento. Estos pasos se pueden combinar en una canalización o un flujo de trabajo. Scheduler es responsable de garantizar que los pasos de este flujo de trabajo se realicen en el orden correcto. Con cada paso realizado, el Programador registra el estado del flujo de trabajo, como "paso aún no iniciado", "paso en ejecución" o "paso completado". La información de estado también debe incluir un límite máximo del tiempo permitido para completar el paso, denominado tiempo límite de finalización. Si un paso requiere acceso a un recurso o servicio remoto, el componente Scheduler invoca al componente Agent adecuado y le pasa los detalles del trabajo que se debe realizar. Normalmente, el componente Scheduler se comunica con un componente Agent a través de la mensajería de solicitud/respuesta asincrónica. Esto se puede implementar mediante colas, aunque también se pueden usar otras tecnologías de mensajería distribuidas.
El componente Scheduler realiza una función similar a la de Process Manager del patrón Process Manager. El flujo de trabajo actual normalmente es definido e implementado por un motor de flujo de trabajo controlado por el Scheduler. Este enfoque separa la lógica de negocios del flujo de trabajo del componente Scheduler.
El agente contiene lógica que encapsula una llamada a un servicio remoto o acceso a un recurso remoto al que hace referencia un paso de una tarea. Cada agente suele encapsular las llamadas a un único servicio o recurso e implementa la lógica de control de errores y reintento adecuadas. Una restricción de tiempo de espera, descrita más adelante en este artículo, se aplica al control de errores y a la lógica de reintento. Al implementar la lógica de reintento, pase un identificador estable en todos los reintentos para que el servicio remoto pueda usarlo para cualquier lógica de desduplicación que pueda tener.
Si los pasos del flujo de trabajo que ejecuta scheduler usan varios servicios y recursos en distintos pasos, cada paso podría hacer referencia a un agente diferente. Este punto es un detalle de implementación del patrón. Para obtener instrucciones sobre cómo diseñar estrategias de reintento, consulte Control de errores transitorios.
Supervisor supervisa el estado de los pasos de la tarea que realiza el componente Scheduler. Se ejecuta periódicamente (la frecuencia será específica del sistema) y examina el estado de los pasos que mantiene el Planificador. Si detecta alguno que haya agotado su tiempo de espera o fallado, coordina con el Agente adecuado para recuperar la etapa o ejecutar la acción correctiva correspondiente (esto puede implicar modificar el estado de una etapa). El programador y los agentes implementan las acciones de recuperación o corrección. El Supervisor solicita que realicen estas acciones.
Scheduler, Agent y Supervisor son componentes lógicos y su implementación física depende de la tecnología utilizada. Por ejemplo, se pueden implementar varios agentes lógicos como parte de un servicio web.
El componente Scheduler conserva la información sobre el progreso de la tarea y el estado de cada paso en un almacén de datos durable que se conoce como el almacén de estado. El Supervisor puede utilizar esta información para ayudar a determinar si un paso ha fallado. En la ilustración se muestra la relación entre el Scheduler, los Agent, el Supervisor y el almacén de estado.
Nota
En este diagrama se muestra una versión simplificada del patrón. En una implementación real, puede haber varias instancias del componente Scheduler que ejecuten un subconjunto de tareas distinto cada una al mismo tiempo. De igual forma, el sistema puede ejecutar múltiples instancias de cada Agent, o incluso varios Supervisors. En este caso, los Supervisores deben coordinar su trabajo entre sí cuidadosamente para garantizar que no compitan por recuperar los mismos pasos y tareas fallidos. El patrón de elección de líder proporciona una posible solución a este problema.
Cuando la aplicación está preparada ejecutar una tarea, envía una solicitud al componente Scheduler. El Scheduler registra la información de estado inicial sobre la tarea y de sus pasos (por ejemplo, paso aún no iniciado) en el almacén de estado y luego inicia las operaciones definidas por el flujo de trabajo. A medida que el Scheduler inicia cada paso, actualiza la información sobre el estado de dicho paso en el almacén de estado (por ejemplo, cuando el paso está en ejecución).
Si un paso hace referencia a un servicio o recurso remoto, el componente Scheduler envía un mensaje al componente Agent adecuado. El mensaje contiene la información que el agente necesita pasar al servicio o acceder al recurso, además del plazo para completar la operación. Si el componente Agent completa la operación correctamente, devuelve una respuesta al componente Scheduler. Entonces, el componente Scheduler actualiza la información de estado en el almacén de estado (por ejemplo, paso completado) y realiza el paso siguiente. Este proceso continúa hasta que la tarea se haya completado.
Un agente puede implementar cualquier lógica de reintento necesaria para realizar su trabajo. Sin embargo, si el componente Agent no completa su trabajo antes de que expire el tiempo de vigencia, el componente Scheduler presupone que se ha producido un error en la operación. En este caso, el componente Agent debe detener su trabajo y no intentar devolver nada a Scheduler (ni siquiera un mensaje de error), ni tampoco intentar cualquier forma de recuperación. La razón de esta restricción es que, cuando se agota el tiempo de espera de un paso o este último produce un error, otra instancia de Agent puede programarse para ejecutar el paso con error (este proceso se describe después).
Si se produce un error en la instancia del componente Agent, el componente Scheduler no recibirá respuesta. El patrón no distingue entre un paso cuyo tiempo de espera se haya agotado y otro que haya producido un error real.
Si se agota el tiempo de espera de un paso o este produce un error, el almacén de estado contendrá un registro que indique que el paso está en ejecución, pero el tiempo de finalización habrá pasado. El Supervisor busca pasos como este e intenta recuperarlos. Una posible estrategia es que el componente Supervisor actualice el valor de vigencia para aumentar el tiempo disponible para completar el paso y que envíe un mensaje al componente Scheduler con la identificación del paso cuyo tiempo de espera se ha agotado. Entonces, el componente Scheduler intentará repetir el paso. Sin embargo, este diseño requiere que las tareas sean idempotentes. El sistema debe contener infraestructura para mantener la coherencia. Para obtener más información, consulte Infraestructura repetible, Diseñar aplicaciones de Azure para resiliencia y disponibilidad y Guía de decisiones sobre la consistencia de recursos.
Puede que el componente Supervisor necesite impedir que se intente realizar el mismo paso, en caso de que produzca errores o su tiempo de espera se agote continuamente. Para ello, el componente Supervisor puede conservar un recuento de reintentos de los pasos junto con la información de estado en el almacén de estado. Si este número supera un umbral predefinido, el componente Supervisor adopta una estrategia de espera durante un tiempo prolongado antes de notificar al componente Scheduler que debe reintentar el paso, con la expectativa de que el error se resuelva durante este tiempo. Como alternativa, el componente Supervisor puede enviar un mensaje al componente Scheduler para solicitar que se deshaga toda la tarea mediante la implementación de un patrón Compensating Transaction. Este enfoque dependerá de que el componente Scheduler y las instancias del componente Agent proporcionen la información necesaria para implementar las operaciones de compensación para cada paso que se haya completado correctamente.
El componente Supervisor no está diseñado para supervisar el componente Scheduler y las instancias del componente Agent y reiniciarlos si se produce un error. Este aspecto del sistema debe controlarse desde la infraestructura donde se ejecutan estos componentes. De forma similar, el Supervisor no debería tener conocimiento de las operaciones empresariales reales que las tareas del Scheduler están ejecutando (incluido cómo compensar en caso de que estas tareas fallen). De esto se ocupa la lógica del flujo de trabajo que implementó el componente Scheduler. La única responsabilidad del Supervisor consiste en determinar si un paso ha fallado y organizar que se repita o revertir la tarea que contiene el paso fallido.
Si el componente Scheduler se reinicia tras un error o si su flujo de trabajo en ejecución finaliza inesperadamente, el componente Scheduler debe ser capaz de determinar el estado de las tareas en proceso que estaba controlando cuando se produjo el error y estar preparado para reanudarlas desde ese punto. Los detalles de la implementación de este proceso suelen ser específicos del sistema. Si no se puede recuperar la tarea, podría ser necesario deshacer el trabajo que la tarea ya haya realizado. También podría ser necesario implementar una transacción compensatoria.
La principal ventaja de este patrón es que el sistema es resistente a errores temporales inesperados o irrecuperables. El sistema puede construirse para que sea de recuperación automática. Por ejemplo, si se produce un error en una instancia de Agente o Programador, se puede iniciar otra y el Supervisor puede organizar la reanudación de la tarea. Si el Supervisor falla, se puede iniciar otra instancia que continúe desde el punto donde ocurrió el fallo. Si la instancia de Supervisor está programada para ejecutarse periódicamente, se puede iniciar automáticamente una nueva después de un intervalo predefinido. El almacén de estado se puede replicar para alcanzar un grado aún mayor de resiliencia.
Problemas y consideraciones
A la hora de decidir cómo implementar este patrón, debe considerar los siguientes puntos:
Este patrón puede ser difícil de implementar y requiere una comprobación exhaustiva de los posibles modos de error del sistema.
La lógica de recuperación/reintento implementada por Scheduler es compleja y depende de la información de estado que se conserva en el almacén de estado. También podría ser necesario registrar la información de implementación de una transacción de compensación en un almacén de datos durables. También se puede producir un error en una transacción compensatoria.
La frecuencia con la que se ejecuta el Supervisor será importante. Debe ejecutarse con la frecuencia suficiente como para evitar que los pasos con error bloqueen una aplicación durante mucho tiempo, pero no demasiada como para sobrecargar.
Los pasos que realiza un Agent pueden ejecutarse más de una vez. La lógica que implementa estos pasos debe ser idempotente.
Cuándo usar este patrón
Utilice este patrón cuando un proceso que se ejecuta en un entorno distribuido, como la nube, debe ser resistente a los errores de comunicación u operativos.
Este patrón puede no ser adecuado para las tareas que no invocan servicios remotos o acceden a recursos remotos.
Diseño de cargas de trabajo
Un arquitecto debe evaluar cómo se puede utilizar el patrón de Supervisor del Agente del Programador en el diseño de su carga de trabajo para abordar los objetivos y principios que se tratan en los pilares del Marco de Arquitectura Bien Diseñada de Azure. Por ejemplo:
| Fundamento | Cómo apoya este patrón los objetivos de los pilares |
|---|---|
| Las decisiones de diseño de la fiabilidad ayudan a que la carga de trabajo sea resistente a los errores y a garantizar que se recupere a un estado de pleno funcionamiento después de que se produzca un error. | Este patrón utiliza métricas de estado para detectar fallos y redirigir las tareas a un agente correcto con el fin de mitigar los efectos de un mal funcionamiento. - RE:05 Redundancia - RE:07 Autosanación |
| La eficiencia del rendimiento ayuda a que la carga de trabajo satisfaga eficazmente las demandas mediante optimizaciones en el escalado, los datos y el código. | Este patrón utiliza métricas de rendimiento y capacidad para detectar la utilización actual y enrutar las tareas a un agente que tenga capacidad. También puede utilizarlo para priorizar la ejecución del trabajo de mayor prioridad sobre el de menor prioridad. - PE:05 Escalado y particionamiento - PE:09 Flujos críticos |
Al igual que con cualquier decisión de diseño, hay que tener en cuenta las ventajas y desventajas con respecto a los objetivos de los otros pilares que podrían introducirse con este patrón.
Ejemplo
Una aplicación web que implementa un sistema de comercio electrónico se ha implementado en Microsoft Azure. Los usuarios pueden ejecutar esta aplicación para examinar los productos disponibles y realizar pedidos. La interfaz de usuario se ejecuta como front-end web y los elementos de procesamiento de pedidos de la aplicación se implementan como un conjunto de trabajos en segundo plano. Parte de la lógica de procesamiento de pedidos implica el acceso a un servicio remoto y este aspecto del sistema podría ser propenso a errores transitorios o más duraderos. Por este motivo, los diseñadores utilizaron el patrón Scheduler Agent Supervisor para implementar los elementos de procesamiento de pedidos del sistema.
Cuando un cliente realiza un pedido, la aplicación construye un mensaje que lo describe y envía este mensaje a una cola. Un proceso de envío independiente, que se ejecuta como un trabajo en segundo plano, recupera el mensaje, inserta los detalles del pedido en la base de datos de pedidos y crea un registro para el proceso de pedido en el almacén de estado. Las inserciones en la base de datos de pedidos y el almacén de estado se realizan como parte de la misma operación. El proceso de envío está diseñado para garantizar que ambas inserciones se completen juntas.
La información de estado que crea el proceso de presentación para el pedido incluye:
OrderID. Identificador del pedido en la base de datos de pedidos.
LockedBy. Identificador de instancia del trabajador que gestiona el pedido. Puede haber varias instancias actuales del trabajador que ejecuta el Programador, pero cada orden solo debe controlarse mediante una sola instancia.
CompleteBy. Tiempo en el que se debe procesar el pedido.
ProcessState. Estado actual de la tarea de control del pedido. Los estados posibles son:
- Pendiente. El pedido se ha creado, pero aún no se ha iniciado el procesamiento.
- Tramitación. El pedido se está procesando en este momento.
- Procesado. El pedido se ha procesado correctamente.
- Error. Se ha producido un error en el procesamiento del pedido.
FailureCount. Número de veces que se ha intentado procesar el pedido.
En esta información de estado, el campo OrderID se copia desde el identificador de pedido del pedido nuevo. Los campos LockedBy y CompleteBy están establecidos en null, el campo ProcessState, en Pending y el campo FailureCount, en 0.
Nota
En este ejemplo, la lógica de control de pedidos es relativamente sencilla y solo tiene un único paso que invoca un servicio remoto. En un escenario más complejo de varios pasos, el proceso de envío probablemente implicaría varios pasos, por lo que se crearían varios registros en el almacén de estado, donde cada uno describirá el estado de un paso individual.
Scheduler también se ejecuta como un trabajo en segundo plano e implementa la lógica de negocios que controla el orden. Una instancia de Scheduler que sondea nuevos pedidos examina el almacén de estado en busca de los registros donde el valor del campo LockedBy sea Null, mientras que el del campo ProcessState sea Pending. Cuando el Scheduler encuentra un nuevo pedido, inmediatamente rellena el campo LockedBy con su propio identificador de instancia, establece el campo CompleteBy en la hora adecuada y el campo ProcessState en procesamiento. El código está diseñado como exclusivo y atómico para garantizar que dos instancias simultáneas de Scheduler no pueden controlar el mismo pedido a la vez.
A continuación, el Planificador ejecuta el flujo de trabajo empresarial para procesar el pedido de forma asincrónica, pasando el valor del campo OrderID desde el almacén de estado. El flujo de trabajo que controla el pedido recupera los detalles de este de la base de datos de pedidos y realiza su trabajo. Cuando un paso del flujo de trabajo de procesamiento del pedido necesita invocar el servicio remoto, utiliza una instancia de Agent. El paso del flujo de trabajo se comunica con el Agente mediante un par de colas de mensajes de Azure Service Bus que actúan como un canal de solicitud/respuesta. En la ilustración se muestra una vista general de la solución.
El mensaje enviado al Agente desde un paso del flujo de trabajo describe el pedido e incluye la hora de finalización. Si el Agente recibe una respuesta del servicio remoto antes de que expire el tiempo límite, envía un mensaje de respuesta en la cola de Service Bus en la que escucha el flujo de trabajo. Cuando el paso del flujo de trabajo recibe el mensaje de respuesta válido, completa el procesamiento y Scheduler establece el campo ProcessState del estado de pedido en "Processed". En este momento, el procesamiento del pedido se ha completado correctamente.
Si el tiempo de finalización expira antes de que el Agente reciba una respuesta del servicio remoto, el Agente detiene su procesamiento y finaliza la gestión del pedido. De forma similar, si el flujo de trabajo que controla el pedido se pasa del tiempo límite para completar, también termina. En ambos casos, el estado del pedido en el almacén de estado sigue como procesando, pero el tiempo de finalización indica que ha transcurrido el tiempo de procesamiento del pedido y se considera que el proceso ha fallado. Si el Agente que tiene acceso al servicio remoto o al flujo de trabajo que controla el pedido finaliza inesperadamente, la información del almacén de estado permanece establecida en procesamiento y, finalmente, tiene un valor completo expirado.
Si el Agente detecta un error irrecuperable y no transitorio al intentar ponerse en contacto con el servicio remoto, puede enviar una respuesta de error al flujo de trabajo. Scheduler puede establecer el estado del pedido en Error y generar un evento que avise a un operador. Entonces, el operador puede intentar resolver manualmente la causa del error y volver a enviar el paso de procesamiento con error.
Supervisor examina periódicamente el almacén de estado en busca de pedidos con valor de tiempo de vigencia expirado. Si el Supervisor encuentra un registro, incrementa el campo FailureCount. Si el valor de FailureCount está por debajo de un umbral determinado, Supervisor restablece el campo LockedBy en Null, actualiza el campo CompleteBy con un nuevo tiempo de vigencia y establece el campo ProcessState en Pending. Una instancia de Scheduler puede recoger este pedido y procesarlo como antes. Si el valor de FailureCount supera un umbral determinado, se presupone que el error no es transitorio. Supervisor establece el estado del pedido en Error y genera un evento que avisa a un operador.
En este ejemplo, el supervisor se implementa como un trabajador en segundo plano independiente. Puede usar varias estrategias para organizar la ejecución de la tarea Supervisor, como el uso de funciones desencadenadas por temporizador en Azure Functions o un trabajo programado en Azure Container Apps.
Aunque no se muestra en este ejemplo, Scheduler puede necesitar mantener informada a la aplicación que envió el pedido sobre el progreso y el estado de este. La aplicación y Scheduler están aislados entre ellos para que no dependan el uno del otro. La aplicación no sabe qué instancia de Scheduler controla el pedido y Scheduler desconoce la instancia de aplicación que lo envió.
Para permitir informar del estado del pedido, la aplicación puede usar su propia cola de respuestas privada. Los detalles de esta cola de respuestas se incluirían como parte de la solicitud enviada al proceso de envío, en la cual se incluiría esta información en el almacén de estado. A continuación, Scheduler publicaría mensajes en esta cola que indican el estado del pedido (por ejemplo, la solicitud recibida, la finalización del pedido y el error del pedido). En estos mensajes debe incluirse el identificador de pedido para que la aplicación los pueda correlacionar con la solicitud original.
Pasos siguientes
Las directrices siguientes también pueden ser importantes a la hora de implementar este patrón:
Guía de mensajería asincrónica. En general, los componentes del patrón Scheduler Agent Supervisor se ejecutan desacoplados entre ellos y se comunican de forma asincrónica. Describe algunos de los posibles enfoques para implementar la comunicación asincrónica basada en colas de mensajes.
Reference 6: A Saga on Sagas (Referencia 6: una saga sobre sagas). Ejemplo que muestra cómo el patrón CQRS utiliza un gestor de procesos (parte de la guía de CQRS).
Recursos relacionados
Los patrones siguientes también podrían ser importantes a la hora de implementar este patrón:
Patrón Retry. Un Agente puede usar este patrón para reintentar de forma transparente una operación que accede a un servicio o recurso remoto que ha fallado anteriormente. Se utiliza cuando se cree que la causa del error es transitoria y se puede corregir.
Patrón de Interruptor de Circuito. Una instancia de Agent puede utilizar este patrón para controlar errores que tardan un tiempo variable en solucionarse durante la conexión a un recurso o servicio remoto.
Patrón de Transacción Compensatoria. Si el flujo de trabajo que está realizando la instancia de Scheduler no se puede completar con éxito, puede ser necesario deshacer el trabajo ya realizado. El patrón Compensating Transaction describe la manera de lograrlo para las operaciones que siguen el modelo de coherencia final. Estos tipos de operaciones las implementa normalmente una instancia de Scheduler que realiza flujos de trabajo y procesos empresariales complejos.
Patrón de Elección de Líder. Podría ser necesario coordinar las acciones de varias instancias de Supervisor para impedir que intenten recuperar el mismo proceso con error. El patrón Leader Election describe cómo hacerlo.
Arquitectura en la nube: patrón Scheduler-Agent-Supervisor en el blog de Clemens Vasters