Compartir a través de


Consideraciones sobre el diseño de aplicaciones para cargas de trabajo críticas

La arquitectura de referencia crítica de línea base usa una sencilla aplicación de catálogo en línea para ilustrar una carga de trabajo altamente confiable. Los usuarios pueden examinar un catálogo de artículos, revisar los detalles de los artículos y publicar clasificaciones y comentarios de los artículos. Este artículo se centra en los aspectos de confiabilidad y resistencia de una aplicación crítica, como el procesamiento asincrónico de solicitudes y cómo lograr un alto rendimiento en una solución.

Importante

Logotipo de GitHub Una implementación de referencia de nivel de producción que muestra el desarrollo de aplicaciones críticas en Azure apoya la orientación de este artículo. Puede usar esta implementación como base para el desarrollo posterior de soluciones en el primer paso hacia la producción.

Composición de la aplicación

En el caso de las aplicaciones críticas a gran escala, debe optimizar la arquitectura para lograr una escalabilidad y resistencia completas. Puede separar los componentes en unidades funcionales que pueden funcionar de forma independiente. Aplique esta separación en todos los niveles de la pila de la aplicación para que cada parte del sistema se pueda escalar de forma independiente y satisfaga los cambios en la demanda. La implementación demuestra este enfoque.

La aplicación usa puntos de conexión de API sin estado que desacoplan las solicitudes de escritura de larga duración de forma asincrónica mediante un agente de mensajería. La composición de la carga de trabajo permite eliminar y volver a crear clústeres completos de Azure Kubernetes Service (AKS) y otras dependencias en la marca en cualquier momento. Los componentes principales de la aplicación son:

  • Interfaz de usuario (UI): una aplicación web de página única a la que pueden acceder los usuarios. La IU se hospeda en el alojamiento web estático de una cuenta de Azure Storage.

  • API (CatalogService): una API REST a la que llama la aplicación de interfaz de usuario, pero que sigue disponible para otras posibles aplicaciones cliente.

  • Trabajo (BackgroundProcessor): trabajo en segundo plano que escucha nuevos eventos en el bus de mensajes y procesa las solicitudes de escritura en la base de datos. Este componente no expone ninguna API.

  • API del servicio de estado (HealthService): API que notifica el estado de la aplicación comprobando si funcionan los componentes críticos, como la base de datos o el bus de mensajería.

    Diagrama que muestra el flujo de la aplicación.

La carga de trabajo consta de la API, el trabajo y las aplicaciones de comprobación de estado. Un espacio de nombres de AKS dedicado denominado workload hospeda la carga de trabajo como contenedores. No hay ninguna comunicación directa entre los pods. Los pods no tienen estado y se pueden escalar de forma independiente.

Diagrama que muestra la composición detallada de la carga de trabajo.

Entre otros componentes auxiliares que se ejecutan en el clúster se incluyen los siguientes:

  • Un controlador de entrada NGINX: enruta las solicitudes entrantes a la carga de trabajo y equilibra la carga entre los pods. El controlador de entrada NGINX se expone mediante Azure Load Balancer con una dirección IP pública, pero solo se accede mediante Azure Front Door.

  • Administrador de certificados: cert-manager de Jetstack aprovisiona certificados de seguridad de la capa de transporte (TLS) mediante Let's Encrypt para las reglas de entrada.

  • Controlador de CSI de almacén de secretos: el proveedor de Azure Key Vault para el controlador CSI del almacén de secretos lee de forma segura los secretos, como las cadenas de conexión de Key Vault.

  • Agente de supervisión: se ajusta la configuración predeterminada de OMSAgentForLinux para reducir la cantidad de datos de supervisión que se envían al área de trabajo de Azure Monitor Log.

Conexión de base de datos

Debido a la naturaleza efímera de los stamps de implementación, evite conservar el estado dentro del stamp en la medida de lo posible. El estado se debe conservar en un almacén de datos externo. Para admitir el objetivo de nivel de servicio (SLO) de confiabilidad, cree un almacén de datos resistente. Se recomienda usar soluciones administradas o de plataforma como servicio (PaaS) junto con bibliotecas nativas del SDK que controlan automáticamente los tiempos de espera, las desconexiones y otros estados de error.

En la implementación de referencia, Azure Cosmos DB actúa como almacén de datos principal para la aplicación. Azure Cosmos DB proporciona escrituras en varias regiones. Cada stamp puede escribir en la réplica de Azure Cosmos DB de la misma región y Azure Cosmos DB controla internamente la replicación de datos y la sincronización entre regiones. Azure Cosmos DB for NoSQL admite todas las funcionalidades del motor de base de datos.

Para más información, consulta Plataforma de datos para cargas de trabajo críticas.

Nota:

Use Azure Cosmos DB for NoSQL para las nuevas aplicaciones. En el caso de las aplicaciones heredadas que usan otro protocolo NoSQL, evalúe la ruta de migración a Azure Cosmos DB.

Para las aplicaciones críticas que priorizan la disponibilidad sobre el rendimiento, se recomiendan las escritura en una sola región y las lecturas de varias regiones con un nivel de coherencia fuerte.

Esta arquitectura usa Storage para almacenar temporalmente el estado en el stamp para los puntos de control de Azure Event Hubs.

Todos los componentes de la carga de trabajo usan el SDK para .NET Core de Azure Cosmos DB para comunicarse con la base de datos. El SDK incluye una lógica sólida para mantener las conexiones de base de datos y controlar los errores. Las opciones de configuración de claves son las siguientes:

  • Modo de conectividad directa: esta configuración es predeterminada para el SDK de .NET v3 porque ofrece un mejor rendimiento. El modo de conectividad directa tiene menos saltos de red en comparación con el modo de puerta de enlace, que usa HTTP.

  • Respuesta de devolución de contenido al escribir: este enfoque está deshabilitado para que el cliente de Azure Cosmos DB no pueda devolver el documento de las operaciones de creación, actualización/inserción, revisión y sustitución, que reducen el tráfico de red. El procesamiento adicional en el cliente no requiere esta configuración.

  • Serialización personalizada: este proceso establece la directiva de nomenclatura de propiedades JSON en JsonNamingPolicy.CamelCase para traducir las propiedades de .NET a propiedades JSON estándar. También puede traducir propiedades JSON a propiedades de .NET. La condición de omisión predeterminada omite las propiedades con valores nulos, como JsonIgnoreCondition.WhenWritingNull, durante la serialización.

  • ApplicationRegion: esta propiedad se establece en la región del stamp, lo que permite al SDK encontrar el punto de conexión más cercano. El punto de conexión debe estar preferiblemente en la misma región.

El siguiente bloque de código aparece en la implementación de referencia:

//
// /src/app/AlwaysOn.Shared/Services/CosmosDbService.cs
//
CosmosClientBuilder clientBuilder = new CosmosClientBuilder(sysConfig.CosmosEndpointUri, sysConfig.CosmosApiKey)
    .WithConnectionModeDirect()
    .WithContentResponseOnWrite(false)
    .WithRequestTimeout(TimeSpan.FromSeconds(sysConfig.ComsosRequestTimeoutSeconds))
    .WithThrottlingRetryOptions(TimeSpan.FromSeconds(sysConfig.ComsosRetryWaitSeconds), sysConfig.ComsosMaxRetryCount)
    .WithCustomSerializer(new CosmosNetSerializer(Globals.JsonSerializerOptions));

if (sysConfig.AzureRegion != "unknown")
{
    clientBuilder = clientBuilder.WithApplicationRegion(sysConfig.AzureRegion);
}

_dbClient = clientBuilder.Build();

Mensajería asincrónica

Al implementar el acoplamiento flexible, los servicios no tienen dependencias en otros servicios. El aspecto débil permite que un servicio funcione de forma independiente. El aspecto de acoplamiento permite la comunicación entre servicios mediante interfaces bien definidas. En el caso de una aplicación crítica, el acoplamiento flexible impide que los errores de bajada se produzcan en cascada a front-end u otras marcas de implementación, lo que proporciona alta disponibilidad.

Entre las características clave de la mensajería asincrónica se incluyen las siguientes:

  • Los servicios no tienen que usar la misma plataforma de proceso, lenguaje de programación o sistema operativo.

  • Los servicios se escalan de forma independiente.

  • Los errores del flujo descendente no afectan a las transacciones de cliente.

  • La integridad transaccional es difícil de mantener, ya que la creación y persistencia de los datos se produce en servicios independientes. La integridad transaccional es un desafío en los servicios de mensajería y persistencia. Para obtener más información, consulte Procesamiento del mensaje Idempotent.

  • El seguimiento integral requiere una orquestación compleja.

Se recomienda usar patrones de diseño conocidos, como el patrón de nivelación de carga basada en colas y el patrón de consumidores competidores. Estos patrones distribuyen la carga del productor a los consumidores y permiten el procesamiento asincrónico por parte de los consumidores. Por ejemplo, el trabajo permite que la API acepte la solicitud y vuelva al autor de llamada rápidamente, mientras que procesa una operación de escritura de base de datos por separado.

Event Hubs intermedia los mensajes entre la API y el trabajo.

Importante

No use el agente de mensajes como almacén de datos persistente durante largos períodos de tiempo. El servicio Event Hubs admite la característica de captura. La característica de captura permite que un centro de eventos escriba automáticamente una copia de los mensajes en una cuenta de almacenamiento vinculada. Este proceso controla el uso y sirve como mecanismo para realizar copias de seguridad de mensajes.

Detalles de implementación de operaciones de escritura

Las operaciones de escritura, como la publicación de la clasificación y la publicación de comentarios, se procesan de forma asincrónica. La API envía primero un mensaje con toda la información pertinente, como el tipo de acción y los datos de los comentarios, a la cola de mensajes y devuelve inmediatamente el código HTTP 202 (Accepted) con el encabezado Location del objeto que se va a crear.

Las instancias de BackgroundProcessor procesan los mensajes de la cola y controlan la comunicación real con la base de datos para las operaciones de escritura. BackgroundProcessor se escala y reduce horizontalmente dinámicamente en función del volumen de mensajes de la cola. El límite de escalado horizontal de las instancias de procesador se define mediante el número máximo de particiones de Event Hubs, que es 32 para los niveles Básico y Estándar, 100 para el nivel Premium y 1024 para el nivel Dedicado.

Diagrama que muestra la naturaleza asincrónica de la característica de publicación de la clasificación en la implementación.

La biblioteca de procesador de Azure Event Hubs de BackgroundProcessor usa Azure Blob Storage para administrar la propiedad de las particiones, el equilibrio de carga entre las diferentes instancias de trabajo y puntos de control para realizar un seguimiento del progreso. Los puntos de control no se escriben en Blob Storage después de cada evento porque agrega un retraso costoso para cada mensaje. En su lugar, los puntos de control se escriben en un bucle de temporizador y puede configurar la duración. El valor predeterminado es 10 segundos.

El siguiente bloque de código aparece en la implementación de referencia:

while (!stoppingToken.IsCancellationRequested)
{
    await Task.Delay(TimeSpan.FromSeconds(_sysConfig.BackendCheckpointLoopSeconds), stoppingToken);
    if (!stoppingToken.IsCancellationRequested && !checkpointEvents.IsEmpty)
    {
        string lastPartition = null;
        try
        {
            foreach (var partition in checkpointEvents.Keys)
            {
                lastPartition = partition;
                if (checkpointEvents.TryRemove(partition, out ProcessEventArgs lastProcessEventArgs))
                {
                    if (lastProcessEventArgs.HasEvent)
                    {
                        _logger.LogDebug("Scheduled checkpointing for partition {partition}. Offset={offset}", partition, lastProcessEventArgs.Data.Offset);
                        await lastProcessEventArgs.UpdateCheckpointAsync();
                    }
                }
            }
        }
        catch (Exception e)
        {
            _logger.LogError(e, "Exception during checkpointing loop for partition={lastPartition}", lastPartition);
        }
    }
}

Si la aplicación del procesador encuentra un error o se detiene antes de procesar el mensaje:

  • Otra instancia recoge el mensaje para su reprocesamiento, ya que no se ha agregado el punto de control correctamente en Azure Storage.

  • Se produce un conflicto si el trabajo anterior conserva el documento en la base de datos antes de que se produzca un error en el trabajo. Este error se produce porque se usan el mismo identificador y clave de partición. El procesador puede omitir el mensaje de forma segura porque el documento ya se conserva.

  • Una nueva instancia repite los pasos y finaliza la persistencia si el trabajo anterior finalizó antes de escribir en la base de datos.

Detalles de implementación de operaciones de lectura

La API procesa directamente las operaciones de lectura y devuelve inmediatamente los datos al usuario.

Diagrama que muestra un proceso de operaciones de lectura.

No se establece un método back-channel para comunicarse con el cliente si la operación se completa correctamente. La aplicación cliente tiene que sondear proactivamente la API para buscar actualizaciones del artículo especificado en el encabezado HTTP Location.

Escalabilidad

Los componentes individuales de la carga de trabajo se deben escalar horizontalmente de forma independiente porque cada uno tiene patrones de carga diferentes. Los requisitos de escalado dependen de la funcionalidad del servicio. Algunos servicios afectan directamente a los usuarios y deben escalar horizontalmente de forma agresiva para garantizar respuestas rápidas y una experiencia positiva del usuario.

La implementación empaqueta los servicios como imágenes de contenedor y usa gráficos de Helm para implementar los servicios en cada marca. Los servicios están configurados para que tengan implementadas las solicitudes y límites esperados de Kubernetes y una regla de escalado automático preconfigurada. Los componentes CatalogService y de la carga de trabajo BackgroundProcessor se pueden escalar y reducir horizontalmente de forma individual porque ambos servicios no tienen estado.

Los usuarios interactúan directamente con CatalogService, por lo que esta parte de la carga de trabajo debe responder bajo cualquier carga. Hay como mínimo tres instancias por cada clúster para su distribución entre las tres zonas de disponibilidad de una región de Azure. El escalador automático de pods horizontal (HPA) de AKS agrega automáticamente más pods según sea necesario. La característica de autoescala de Azure Cosmos DB puede aumentar y reducir dinámicamente las unidades de solicitud (RU) disponibles para la colección. CatalogService y Azure Cosmos DB se combinan para formar una unidad de escalado dentro de un stamp.

HPA se implementa con un gráfico de Helm que tiene un número máximo y mínimo configurable de réplicas. La prueba de carga determinó que cada instancia puede controlar aproximadamente 250 solicitudes por segundo con un patrón de uso estándar.

El servicio BackgroundProcessor tiene requisitos diferentes y se considera un trabajo en segundo plano que tiene un impacto limitado en la experiencia del usuario. Por lo tanto, BackgroundProcessor tiene una configuración de escalado automático diferente en comparación con CatalogService y puede escalar entre 2 y 32 instancias. Determine este límite en función del número de particiones que se usan en los centros de eventos. No necesita más trabajos que las particiones.

Componente minReplicas maxReplicas
CatalogService 3 20
BackgroundProcessor 2 32

Cada componente de la carga de trabajo que incluye dependencias como ingress-nginx, tiene configurados presupuestos de interrupciones de pods (PDB) para asegurarse de que esté siempre disponible un número mínimo de instancias cuando se implementen cambios en los clústeres.

El siguiente bloque de código aparece en la implementación de referencia:

#
# /src/app/charts/healthservice/templates/pdb.yaml
# Example pod distribution budget configuration.
#
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: {{ .Chart.Name }}-pdb
spec:
  minAvailable: 1
  selector:
    matchLabels:
      app: {{ .Chart.Name }}

Nota:

Determine el número mínimo y el número máximo real de pods para cada componente a través de pruebas de carga. El número de pods puede diferir para cada carga de trabajo.

Instrumentación

Utilice la instrumentación para evaluar el rendimiento, los cuellos de botella y los problemas de estado que los componentes de la carga de trabajo pueden introducir en el sistema. Para ayudarle a cuantificar las decisiones, cada componente debe emitir información suficiente mediante métricas y registros de seguimiento. Tenga en cuenta las siguientes consideraciones clave al instrumentar la aplicación:

  • Envíe los registros, las métricas y otros datos de telemetría adicionales al sistema de registro del stamp.
  • Use el registro estructurado en lugar de texto sin formato para que pueda consultar la información.
  • Implemente la correlación de eventos para obtener una vista de transacciones completa. En la implementación de referencia, cada respuesta de API contiene un identificador de operación como un encabezado HTTP para la trazabilidad.
  • No confíe solo en el registro de stdout o consola. Pero puede usar estos registros para solucionar inmediatamente un error en un pod.

Esta arquitectura implementa el seguimiento distribuido con Application Insights y el área de trabajo de Azure Monitor Logs para todos los datos de supervisión de la aplicación. Use Azure Monitor Logs para los registros y las métricas de los componentes de la infraestructura y la carga de trabajo. Esta arquitectura implementa el seguimiento completo de un extremo a otro de las solicitudes que proceden de la API, pasan por Event Hubs y, a continuación, a Azure Cosmos DB.

Importante

Implemente recursos de supervisión de stamps en un grupo de recursos de supervisión independiente. Los recursos tienen un ciclo de vida diferente al de la propia marca. Para obtener más información, consulte Supervisión de los datos de los recursos de stamp.

Diagrama de la implementación del stamp, los servicios de supervisión y los servicios globales de forma independiente.

Detalles de implementación de la supervisión de la aplicación

El componente BackgroundProcessor usa el paquete NuGet Microsoft.ApplicationInsights.WorkerService para obtener la instrumentación integrada de la aplicación. Serilog también se usa para todo el registro dentro de la aplicación. Application Insights se configura como receptor además del receptor de la consola. Solo cuando es necesario realizar un seguimiento de métricas adicionales, se usa directamente una instancia de TelemetryClient para Application Insights.

El siguiente bloque de código aparece en la implementación de referencia:

//
// /src/app/AlwaysOn.BackgroundProcessor/Program.cs
//
public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
    .ConfigureServices((hostContext, services) =>
    {
        Log.Logger = new LoggerConfiguration()
                            .ReadFrom.Configuration(hostContext.Configuration)
                            .Enrich.FromLogContext()
                            .WriteTo.Console(outputTemplate: "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} {Level:u3}] {Message:lj} {Properties:j}{NewLine}{Exception}")
                            .WriteTo.ApplicationInsights(hostContext.Configuration[SysConfiguration.ApplicationInsightsConnStringKeyName], TelemetryConverter.Traces)
                            .CreateLogger();
    }

Captura de pantalla de la funcionalidad de seguimiento integral.

Para demostrar la trazabilidad práctica de las solicitudes, cada solicitud de API correcta e incorrecta devuelve el encabezado del identificador de correlación al autor de llamada. Con este identificador, el equipo de soporte técnico de la aplicación puede buscar en Application Insights y obtener una vista detallada de la transacción completa, que se ilustra en el diagrama anterior.

El siguiente bloque de código aparece en la implementación de referencia:

//
// /src/app/AlwaysOn.CatalogService/Startup.cs
//
app.Use(async (context, next) =>
{
    context.Response.OnStarting(o =>
    {
        if (o is HttpContext ctx)
        {
            // ... code omitted for brevity
            context.Response.Headers.Add("Server-Location", sysConfig.AzureRegion);
            context.Response.Headers.Add("Correlation-ID", Activity.Current?.RootId);
            context.Response.Headers.Add("Requested-Api-Version", ctx.GetRequestedApiVersion()?.ToString());
        }
        return Task.CompletedTask;
    }, context);
    await next();
});

Nota:

El muestreo adaptable está habilitado de manera predeterminada en el SDK de Application Insights. Por muestreo adaptable se entiende que no todas las solicitudes se envían a la nube y se pueden buscar por identificador. Los equipos de las aplicaciones críticas deben poder realizar un seguimiento confiable de cada solicitud, que es por lo que la implementación de referencia tiene deshabilitado el muestreo adaptable en el entorno de producción.

Detalles de implementación de la supervisión de Kubernetes

Puede usar la configuración de diagnóstico para enviar registros y métricas de AKS a los registros de Azure Monitor. También puede usar la característica de Container Insights con AKS. Habilite Container Insights para implementar OMSAgentForLinux mediante un DaemonSet de Kubernetes en cada uno de los nodos de los clústeres de AKS. OMSAgentForLinux puede recopilar más registros y métricas desde el clúster de Kubernetes y enviarlos a su área de trabajo de Azure Monitor Logs correspondiente. Esta área de trabajo contiene datos más detallados sobre los pods, las implementaciones, los servicios y el estado general del clúster.

Un registro extenso puede afectar negativamente al coste y no proporciona ninguna ventaja. Por este motivo, la recopilación de registros de stdout y la extracción de Prometheus están deshabilitadas para los pods de la carga de trabajo en la configuración de Container Insights, puesto que todos los seguimientos ya se capturan mediante Application Insights, que generan registros duplicados.

El siguiente bloque de código aparece en la implementación de referencia:

#
# /src/config/monitoring/container-azm-ms-agentconfig.yaml
# This is just a snippet showing the relevant part.
#
[log_collection_settings]
    [log_collection_settings.stdout]
        enabled = false

        exclude_namespaces = ["kube-system"]

Para obtener más información, consulte el archivo de configuración completo.

Supervisión del estado de aplicación

Puede usar la supervisión y la observabilidad de las aplicaciones para identificar rápidamente problemas con un sistema e informar al modelo de estado sobre el estado actual de la aplicación. Puede exponer la supervisión del estado a través de puntos de conexión de mantenimiento. Los sondeos de estado usan datos de supervisión de estado para proporcionar información. El equilibrador de carga principal usa esa información para quitar inmediatamente el componente incorrecto de la rotación.

Esta arquitectura aplica el seguimiento de estado en los siguientes niveles:

  • Pods de carga de trabajo que se ejecutan en AKS. Estos pods tienen sondeos de estado y de ejecución, por lo que AKS administra su ciclo de vida.

  • El servicio de estado es un componente dedicado en el clúster. Azure Front Door está configurado para sondear el servicio de estado de cada stamp y quitar automáticamente los stamps incorrectos del equilibrio de carga.

Detalles de implementación del servicio de estado

HealthService es un componente de la carga de trabajo que se ejecuta junto con otros componentes, como CatalogService y BackgroundProcessor en el clúster de proceso. HealthService proporciona una API REST a la que llama la comprobación de estado de Azure Front Door para determinar la disponibilidad de un stamp. A diferencia de los sondeos de ejecución básicos, el servicio de estado es un componente más complejo que proporciona el estado de las dependencias además del propio.

Diagrama del servicio de salud consultando Azure Cosmos DB, Event Hubs y Storage.

Si el clúster de AKS está inactivo, el servicio de estado no responde, lo que representa una carga de trabajo en estado incorrecto. Cuando se ejecuta el servicio, realiza comprobaciones periódicas en los componentes críticos de la solución. Todas las comprobaciones se realizan de forma asincrónica y en paralelo. Si se produce un error en alguna de las comprobaciones, la marca completa no está disponible.

Advertencia

Los sondeos de estado de Azure Front Door pueden imponer una carga significativa en el servicio de estado, ya que las solicitudes proceden de varias ubicaciones de punto de presencia (PoP). Para evitar sobrecargar los componentes de bajada, implemente el almacenamiento en caché eficaz.

El servicio de estado también se usa para las pruebas de ping de dirección URL configuradas explícitamente con el recurso de Application Insights de cada stamp.

Para obtener más información sobre la implementación de HealthService, consulte Servicio de estado de aplicación.

Paso siguiente