Diseño de aplicaciones de cargas de trabajo críticas en Azure

Al diseñar una aplicación, los requisitos de aplicación funcionales y no funcionales son críticos. En este área de diseño se describen los patrones de arquitectura y las estrategias de escalado que pueden ayudar a que la aplicación sea resistente a errores.

Importante

Este artículo forma parte de la serie de cargas de trabajo críticas de Azure Well-Architected Framework. Si no está familiarizado con esta serie, se recomienda empezar con ¿Qué es una carga de trabajo crítica?.

Arquitectura de unidad de escalado

Todos los aspectos funcionales de una solución deben ser capaces de escalar para satisfacer los cambios en la demanda, idealmente el escalado automático en respuesta a la carga. Se recomienda usar una arquitectura de unidad de escalado para optimizar la escalabilidad de un extremo a otro a través de la compartimentación y también para estandarizar el proceso de agregar y quitar capacidad. Una unidad de escalado es una unidad lógica o función que se puede escalar de forma independiente. Una unidad se puede componer de componentes de código, plataformas de hospedaje de aplicaciones, marcas de implementación que cubren los componentes relacionados e incluso suscripciones para admitir requisitos multiinquilino.

Se recomienda este enfoque porque aborda los límites de escala de los recursos individuales y toda la aplicación. Ayuda con escenarios complejos de implementación y actualización, ya que una unidad de escalado se puede implementar como una unidad. Además, puede probar y validar versiones específicas de componentes en una unidad antes de dirigirle el tráfico del usuario.

Supongamos que la aplicación crítica es un catálogo de productos en línea. Tiene un flujo de usuario para procesar comentarios y clasificaciones del producto. El flujo usa API para recuperar y publicar comentarios y clasificaciones, así como componentes auxiliares como un punto de conexión de OAuth, un almacén de datos y colas de mensajes. Los puntos de conexión de API sin estado representan unidades funcionales granulares que deben adaptarse a los cambios a petición. La plataforma de aplicaciones subyacente también debe ser capaz de escalar en consecuencia. Para evitar cuellos de botella de rendimiento, los componentes y las dependencias de bajada también deben escalar a un grado adecuado. Pueden escalar de forma independiente, como unidades de escalado independientes o juntas, como parte de una sola unidad lógica.

Unidades de escalado de ejemplo

En la imagen siguiente se muestran los posibles ámbitos de las unidades de escalado. Los ámbitos van desde pods de microservicios hasta nodos de clúster y marcas de implementación regionales.

Diagrama que muestra varios ámbitos para las unidades de escalado.

Consideraciones de diseño

  • Ámbito. El ámbito de una unidad de escalado, la relación entre las unidades de escalado y sus componentes deben definirse según un modelo de capacidad. Tenga en cuenta los requisitos no funcionales para el rendimiento.

  • Límites de escalado. Es posible que los límites y cuotas de escalado de suscripciones de Azure tengan un efecto en el diseño de aplicaciones, las opciones tecnológicas y la definición de unidades de escalado. Las unidades de escalado pueden ayudarle a omitir los límites de escala de un servicio. Por ejemplo, si un clúster de AKS en una unidad solo puede tener 1000 nodos, puede usar dos unidades para aumentar ese límite a 2000 nodos.

  • Carga esperada. Use el número de solicitudes para cada flujo de usuario, la tasa de solicitudes máximas esperada (solicitudes por segundo) y patrones de tráfico diarios, semanales o estacionales para informar a los requisitos de escalado principal. Tenga en cuenta también los patrones de crecimiento esperados para el tráfico y el volumen de datos.

  • Rendimiento degradado aceptable. Determine si un servicio degradado con tiempos de respuesta elevados es aceptable bajo carga. Al modelar la capacidad necesaria, el rendimiento necesario de la solución bajo carga es un factor crítico.

  • Requisitos no funcionales. Los escenarios técnicos y empresariales tienen distintas consideraciones sobre resistencia, disponibilidad, latencia, capacidad y observabilidad. Analice estos requisitos en el contexto de los flujos de usuario de un extremo a otro clave. Tendrá flexibilidad relativa en el diseño, la toma de decisiones y las opciones tecnológicas en un nivel de flujo de usuario.

Recomendaciones de diseño

  • Defina el ámbito de una unidad de escalado y los límites que desencadenarán la unidad a escalar.

  • Asegúrese de que todos los componentes de la aplicación se pueden escalar de forma independiente o como parte de una unidad de escalado que incluya otros componentes relacionados.

  • Defina la relación entre las unidades de escalado, en función de un modelo de capacidad y los requisitos no funcionales.

  • Defina una marca de implementación regional para unificar el aprovisionamiento, la administración y el funcionamiento de los recursos de aplicación regionales en una unidad de escalado heterogéneo pero interdependente. A medida que aumenta la carga, se pueden implementar sellos adicionales, dentro de la misma región de Azure o en otras diferentes, para escalar horizontalmente la solución.

  • Use una suscripción de Azure como unidad de escalado para que los límites de escalado dentro de una sola suscripción no restrinjan la escalabilidad. Este enfoque se aplica a escenarios de aplicaciones a gran escala que tienen un volumen de tráfico significativo.

  • Modele la capacidad necesaria en torno a los patrones de tráfico identificados para asegurarse de que la capacidad suficiente se aprovisiona en horas punta para evitar la degradación del servicio. Como alternativa, optimice la capacidad durante las horas de poca actividad.

  • Mida el tiempo necesario para realizar operaciones de escalado horizontal y escalado horizontal para asegurarse de que las variaciones naturales del tráfico no crean un nivel inaceptable de degradación del servicio. Realice un seguimiento de las duraciones de la operación de escalado como una métrica operativa.

Nota:

Al implementar en una zona de aterrizaje de Azure, asegúrese de que la suscripción a la zona de aterrizaje está dedicada a la aplicación para proporcionar un límite de administración claro y para evitar el antipatrón Vecino ruidoso.

Distribución global

Es imposible evitar errores en cualquier entorno altamente distribuido. En esta sección se proporcionan estrategias para mitigar muchos escenarios de error. La aplicación debe ser capaz de soportar errores regionales y zonales. Debe implementarse en un modelo activo o activo para que la carga se distribuya entre todas las regiones.

Vea este vídeo para obtener información general sobre cómo planear errores en aplicaciones críticas y maximizar la resistencia:

Consideraciones de diseño

  • Redundancia. La aplicación debe implementarse en varias regiones. Además, dentro de una región, se recomienda encarecidamente usar zonas de disponibilidad para permitir la tolerancia a errores en el nivel del centro de datos. Las zonas de disponibilidad tienen un perímetro de latencia inferior a 2 milisegundos entre zonas de disponibilidad. En el caso de las cargas de trabajo que son "chatty" entre zonas, esta latencia puede introducir una penalización de rendimiento para la transferencia de datos entre zonas.

  • Modelo activo/activo. Se recomienda una estrategia de implementación activa o activa porque maximiza la disponibilidad y proporciona un acuerdo de nivel de servicio (SLA) compuesto más alto. Sin embargo, puede presentar desafíos relacionados con la sincronización de datos y la coherencia para muchos escenarios de aplicación. Abordar los desafíos en un nivel de plataforma de datos al considerar los inconvenientes de un mayor costo y esfuerzo de ingeniería.

    Una implementación activa o activa en varios proveedores de nube es una manera de mitigar potencialmente la dependencia de los recursos globales dentro de un único proveedor de nube. Sin embargo, una estrategia de implementación activa/activa multinube presenta una cantidad significativa de complejidad en torno a CI/CD. Además, dadas las diferencias en las especificaciones y funcionalidades de los recursos entre los proveedores de nube, necesitaría sellos de implementación especializados para cada nube.

  • Distribución geográfica. La carga de trabajo puede tener requisitos de cumplimiento para la residencia de datos geográficas, la protección de datos y la retención de datos. Considere si hay regiones específicas en las que los datos deben residir o dónde deben implementarse los recursos.

  • Origen de la solicitud. La proximidad geográfica y la densidad de los usuarios o sistemas dependientes deben informar sobre las decisiones de diseño sobre la distribución global.

  • Conectividad. La forma en que los usuarios o sistemas externos acceden a la carga de trabajo influirán en el diseño. Tenga en cuenta si la aplicación está disponible a través de la red pública de Internet o las redes privadas que usan circuitos VPN o Azure ExpressRoute.

Para obtener recomendaciones de diseño y opciones de configuración en el nivel de plataforma, consulte Plataforma de aplicaciones: Distribución global.

Arquitectura controlada por eventos acoplada de forma flexible

El acoplamiento permite la comunicación entre servicios a través de interfaces bien definidas. Un acoplamiento flexible permite que un componente de aplicación funcione de forma independiente. Un estilo de arquitectura de microservicios es coherente con los requisitos críticos. Facilita la alta disponibilidad evitando errores en cascada.

Para acoplamiento flexible, se recomienda encarecidamente incorporar el diseño controlado por eventos. El procesamiento asincrónico de mensajes a través de un intermediario puede crear resistencia.

Diagrama que muestra la comunicación asincrónica controlada por eventos.

En algunos escenarios, las aplicaciones pueden combinar acoplamiento flexible y ajustado, en función de los objetivos empresariales.

Consideraciones de diseño

  • Dependencias en tiempo de ejecución. Los servicios acoplados de forma flexible no deben restringirse para usar la misma plataforma de proceso, lenguaje de programación, tiempo de ejecución o sistema operativo.

  • Escalado. Los servicios deben ser capaces de escalar de forma independiente. Optimice el uso de recursos de infraestructura y plataforma.

  • Tolerancia a errores. Los errores se deben controlar por separado y no deben afectar a las transacciones de cliente.

  • Integridad transaccional. Considere el efecto de la creación y persistencia de datos que se produce en servicios independientes.

  • Seguimiento distribuido. El seguimiento de un extremo a otro podría requerir una orquestación compleja.

Recomendaciones de diseño

  • Alinee los límites de microservicios con flujos de usuario críticos.

  • Use la comunicación asincrónica controlada por eventos siempre que sea posible para admitir una escala sostenible y un rendimiento óptimo.

  • Use patrones como bandeja de salida y sesión transaccional para garantizar la coherencia para que todos los mensajes se procesen correctamente.

Ejemplo: Enfoque controlado por eventos

La implementación de referencia de Mission-Critical Online usa microservicios para procesar una sola transacción empresarial. Aplica operaciones de escritura de forma asincrónica con un agente de mensajes y un trabajo. Las operaciones de lectura son sincrónicas, con el resultado devuelto directamente al autor de la llamada.

Diagrama que muestra la comunicación controlada por eventos.

Patrones de resistencia y control de errores en el código de la aplicación

Una aplicación crítica debe diseñarse para ser resistente para que solucione tantos escenarios de error como sea posible. Esta resistencia maximiza la disponibilidad y confiabilidad del servicio. La aplicación debe tener funcionalidades de recuperación automática, que puede implementar mediante patrones de diseño como Reintentos con Retroceso y Interruptor.

En el caso de errores no transitorios que no se pueden mitigar completamente en la lógica de la aplicación, el modelo de mantenimiento y los contenedores operativos deben tomar medidas correctivas. El código de aplicación debe incorporar la instrumentación y el registro adecuados para informar al modelo de mantenimiento y facilitar la solución de problemas posterior o el análisis de la causa principal según sea necesario. Debe implementar el seguimiento distribuido para proporcionar al autor de la llamada un mensaje de error completo que incluya un identificador de correlación cuando se produzca un error.

Herramientas como Application Insights pueden ayudarle a consultar, correlacionar y visualizar seguimientos de aplicaciones.

Consideraciones de diseño

  • Configuraciones adecuadas. No es raro que los problemas transitorios causen errores en cascada. Por ejemplo, el reintento sin el retroceso adecuado agrava el problema cuando se está limitando un servicio. Puede espaciar los retrasos de reintento linealmente o aumentarlos exponencialmente para retroceder a través de retrasos crecientes.

  • Puntos de conexión de mantenimiento. Puede exponer comprobaciones funcionales en el código de aplicación mediante puntos de conexión de estado que las soluciones externas pueden sondear para recuperar el estado de mantenimiento de los componentes de la aplicación.

Recomendaciones de diseño

Estos son algunos patrones comunes de ingeniería de software para aplicaciones resistentes:

Patrón Resumen
Queue-Based Load Leveling Introduce un búfer entre los consumidores y los recursos solicitados para garantizar niveles de carga coherentes. A medida que se ponen en cola las solicitudes de consumidor, un proceso de trabajo los controla en el recurso solicitado a un ritmo establecido por el trabajo y por la capacidad del recurso solicitado para procesar las solicitudes. Si los consumidores esperan respuestas a sus solicitudes, debe implementar un mecanismo de respuesta independiente. Aplique un orden priorizado para que las actividades más importantes se realicen primero.
Circuit Breaker Proporciona estabilidad esperando la recuperación o rechazando rápidamente las solicitudes en lugar de bloquear mientras espera un servicio remoto o recurso no disponible. Este patrón también controla los errores que pueden tardar una cantidad variable de tiempo en recuperarse cuando se realiza una conexión a un servicio remoto o recurso.
Bulkhead Intenta crear particiones de instancias de servicio en grupos en función de los requisitos de carga y disponibilidad, aislando los errores para mantener la funcionalidad del servicio.
Saga Administra la coherencia de los datos entre microservicios que tienen almacenes de datos independientes asegurándose de que los servicios se actualizan entre sí a través de canales de mensajes o eventos definidos. Cada servicio realiza transacciones locales para actualizar su propio estado y publica un evento para desencadenar la siguiente transacción local en la saga. Si se produce un error en una actualización del servicio, la saga ejecuta transacciones compensatorias para contrarrestar los pasos anteriores a la actualización del servicio. Los pasos de actualización de servicio individuales pueden implementar patrones de resistencia, como reintentos.
Health Endpoint Monitoring Implementa comprobaciones funcionales en una aplicación a la que las herramientas externas pueden acceder a través de puntos de conexión expuestos a intervalos regulares. Puede interpretar las respuestas de los puntos de conexión mediante métricas operativas clave para informar al estado de la aplicación y desencadenar respuestas operativas, como generar una alerta o realizar una implementación de reversión compensatoria.
Reintentar Controla los errores transitorios de forma elegante y transparente.
- Cancelar si es poco probable que el error sea transitorio y es poco probable que se realice correctamente si la operación se intenta de nuevo.
- Reintentar si el error es inusual o poco frecuente y es probable que la operación se realice correctamente si se intenta de nuevo inmediatamente.
- Reintentar después de un retraso si el error se debe a una condición que podría necesitar un breve tiempo para recuperarse, como la conectividad de red o errores de alta carga. Aplique una estrategia de retroceso adecuada a medida que aumenten los retrasos de reintento.
Limitaciones Controla el consumo de recursos usados por los componentes de la aplicación, protegiéndolos de convertirse en sobrecoedidos. Cuando un recurso alcanza un umbral de carga, aplaza las operaciones de prioridad inferior y degrada la funcionalidad no esencial para que la funcionalidad esencial pueda continuar hasta que haya suficientes recursos disponibles para volver al funcionamiento normal.

Estas son algunas recomendaciones adicionales:

  • Use los SDK proporcionados por el proveedor, como los SDK de Azure, para conectarse a servicios dependientes. Use funcionalidades de resistencia integradas en lugar de implementar funcionalidades personalizadas.

  • Aplique una estrategia de retroceso adecuada al reintentar llamadas de dependencia con error para evitar un escenario DDoS autoinfligido.

  • Defina criterios de ingeniería comunes para todos los equipos de microservicios de aplicaciones para impulsar la coherencia y la velocidad en el uso de patrones de resistencia de nivel de aplicación.

  • Implemente patrones de resistencia mediante paquetes estandarizados probados, como Polly para C# o Sentinel para Java.

  • Use identificadores de correlación para todos los eventos de seguimiento y mensajes de registro para vincularlos a una solicitud determinada. Devuelva los identificadores de correlación al autor de la llamada para todas las llamadas, no solo las solicitudes con error.

  • Use el registro estructurado para todos los mensajes de registro. Seleccione un receptor de datos operativos unificado para seguimientos de aplicaciones, métricas y registros para permitir que los operadores puedan depurar fácilmente problemas. Para obtener más información, consulte Recopilar, agregar y almacenar datos de supervisión para aplicaciones en la nube.

  • Asegúrese de que los datos operativos se usan junto con los requisitos empresariales para informar a un modelo de estado de la aplicación.

Selección del lenguaje de programación

Es importante seleccionar los marcos y lenguajes de programación adecuados. Estas decisiones suelen estar controladas por los conjuntos de aptitudes o las tecnologías estandarizadas de la organización. Sin embargo, es esencial evaluar el rendimiento, la resistencia y las funcionalidades generales de varios lenguajes y marcos.

Consideraciones de diseño

  • Funcionalidades del kit de desarrollo. Existen diferencias en las funcionalidades que ofrecen los SDK de servicio de Azure en varios lenguajes. Estas diferencias pueden influir en la elección de un servicio de Azure o lenguaje de programación. Por ejemplo, si Azure Cosmos DB es una opción factible, Es posible que Go no sea un lenguaje de desarrollo adecuado porque no hay ningún SDK de primera entidad.

  • Actualizaciones de características. Considere la frecuencia con la que se actualiza el SDK con nuevas características para el idioma seleccionado. Los SDK usados habitualmente, como las bibliotecas de .NET y Java, se actualizan con frecuencia. Es posible que otros SDK o SDK para otros lenguajes se actualicen con menos frecuencia.

  • Varios lenguajes de programación o marcos de trabajo. Puede usar varias tecnologías para admitir varias cargas de trabajo compuestas. Sin embargo, debe evitar la expansión porque presenta complejidad de administración y desafíos operativos.

  • Opción de proceso. Es posible que el software heredado o propietario no se ejecute en los servicios PaaS. Además, es posible que no pueda incluir software heredado o propietario en contenedores.

Recomendaciones de diseño

  • Evalúe todos los SDK de Azure pertinentes para las funcionalidades que necesita y los lenguajes de programación elegidos. Compruebe la alineación con los requisitos no funcionales.

  • Optimice la selección de lenguajes de programación y marcos en el nivel de microservicio. Use varias tecnologías según corresponda.

  • Priorice el SDK de .NET para optimizar la confiabilidad y el rendimiento. Los SDK de Azure de .NET suelen proporcionar más funcionalidades y se actualizan con frecuencia.

Paso siguiente

Revise las consideraciones para la plataforma de aplicaciones.