Partager via


Considérations relatives à la conception d’application pour les charges de travail stratégiques

L'architecture de référence pour les missions critiques utilise une simple application de catalogue en ligne pour illustrer une charge de travail très fiable. Les utilisateurs peuvent parcourir un catalogue d'articles, consulter les détails des articles et publier des évaluations et des commentaires sur les articles. Cet article se concentre sur les aspects de fiabilité et de résilience d'une application critique, comme le traitement asynchrone des requêtes, et sur la manière d'obtenir un débit élevé dans le cadre d'une solution.

Important

Logo GitHubUne mise en œuvre de référence de niveau production qui présente le développement d'applications critiques sur Azure vient étayer les conseils de cet article. Vous pouvez utiliser cette mise en œuvre comme base pour le développement ultérieur de la solution lors de votre première étape vers la production.

Composition de l’application

Pour les applications critiques à grande échelle, vous devez optimiser l'architecture pour assurer l'évolutivité et la résilience de bout en bout. Vous pouvez séparer les composants en unités fonctionnelles qui peuvent fonctionner indépendamment les unes des autres. Apply this separation at all levels on the application stack so that each part of the system can scale independently and meet changes in demand. La mise en œuvre démontre cette approche.

L’application utilise des points de terminaison d’API sans état qui dissocient de manière asynchrone les demandes d’écriture longues par le biais d’un répartiteur de messagerie. La composition de la charge de travail vous permet de supprimer et de recréer à tout moment des clusters entiers d'Azure Kubernetes Service (AKS) et d'autres dépendances dans le cachet. Les principaux composants de l'application sont les suivants :

  • Interface utilisateur (IU) : Une application Web d'une seule page à laquelle les utilisateurs peuvent accéder. L'interface utilisateur est hébergée dans l'hébergement statique d'un compte Azure Storage.

  • API (CatalogService) : Une API REST qui est appelée par l'application de l'interface utilisateur mais qui reste disponible pour d'autres applications clientes potentielles.

  • Worker (BackgroundProcessor) : Agent d'arrière-plan qui écoute les nouveaux événements sur le bus de messages et qui traite les requêtes d'écriture dans la base de données. Ce composant n’expose aucune API.

  • API Health service (HealthService) : API qui signale l'état de santé de l'application en vérifiant si les composants critiques fonctionnent, tels que la base de données ou le bus de messagerie.

    Diagramme montrant le flux de l'application.

La charge de travail se compose des applications API, worker et de contrôle de santé. Un espace de noms AKS dédié, appelé workload, héberge la charge de travail sous forme de conteneurs. Aucune communication directe n'a lieu entre les modules. Les pods sont sans état et peuvent se mettre à l’échelle indépendamment.

Diagramme illustrant la composition détaillée de la charge de travail.

Les autres composants de support qui s'exécutent dans le cluster comprennent :

  • Un contrôleur d'entrée NGINX : Achemine les requêtes entrantes vers la charge de travail et équilibre la charge entre les pods. Le contrôleur d'entrée NGINX est exposé via Azure Load Balancer avec une adresse IP publique mais n'est accessible que via Azure Front Door.

  • Gestionnaire de certificats : Le cert-manager de Jetstack fournit automatiquement des certificats TLS (Transport Layer Security) en utilisant Let's Encrypt pour les règles d'entrée.

  • Pilote CSI de Secrets Store : Le fournisseur Azure Key Vault pour Secrets Store CSI Driver lit en toute sécurité les secrets, tels que les chaînes de connexion, à partir de Key Vault.

  • Agent de surveillance : La configuration par défaut de OMSAgentForLinux est ajustée pour réduire la quantité de données de surveillance envoyées à l'espace de travail Azure Monitor Logs.

Connexion de base de données

En raison de la nature éphémère des empreintes de déploiement, évitez de conserver l’état dans l’empreinte autant que possible. Vous devez conserver l'état dans un magasin de données externalisé. Pour prendre en charge l'objectif de niveau de service (SLO) de fiabilité, créez un magasin de données résilient. Nous vous recommandons d'utiliser des solutions gérées ou une plateforme en tant que service (PaaS), en combinaison avec des bibliothèques SDK natives qui gèrent automatiquement les dépassements de délai, les déconnexions et d'autres états d'échec.

Dans l’implémentation de référence, Azure Cosmos DB sert de magasin de données principal pour l’application. Azure Cosmos DB permet d'écrire dans plusieurs régions. Chaque timbre peut écrire sur la réplica Azure Cosmos DB dans la même région, et Azure Cosmos DB gère en interne la réplication et la synchronisation des données entre les régions. Azure Cosmos DB for NoSQL prend en charge toutes les fonctionnalités du moteur de base de données.

Pour plus d’informations, consultez Plateforme de données pour les charges de travail stratégiques.

Remarque

Utilisez Azure Cosmos DB for NoSQL pour de nouvelles applications. Pour les applications héritées qui utilisent un autre protocole NoSQL, évaluez le chemin de migration vers Azure Cosmos DB.

Pour les applications critiques qui privilégient la disponibilité aux performances, nous recommandons l'écriture dans une seule région et la lecture dans plusieurs régions avec un niveau de cohérence élevé.

Cette architecture utilise le stockage pour stocker temporairement l'état dans le hub pour le point de contrôle Azure Event Hubs.

Tous les composants de charge de travail utilisent le kit SDK .NET Core Azure Cosmos DB pour communiquer avec la base de données. Le SDK inclut une logique robuste pour maintenir les connexions de base de données et gérer les défaillances. Les principaux paramètres de configuration sont les suivants

  • Mode de connectivité directe : Ce paramètre est défini par défaut pour le SDK .NET v3, car il offre de meilleures performances. Le mode de connectivité directe comporte moins de sauts de réseau que le mode passerelle, qui utilise HTTP.

  • Renvoyer la réponse du contenu en cas d'écriture : Cette approche est désactivée pour que le client Azure Cosmos DB ne puisse pas renvoyer le document à partir des opérations de création, d'upsert et de patcher et remplacer, ce qui réduit le trafic réseau. Le traitement ultérieur sur le client ne nécessite pas ce paramètre.

  • Sérialisation personnalisée : Ce processus définit la stratégie de dénomination des propriétés JSON sur JsonNamingPolicy.CamelCase pour traduire les propriétés .NET en propriétés JSON standard. Il peut également traduire les propriétés JSON en propriétés .NET. La condition d'ignorance par défaut ignore les propriétés avec des valeurs nulles, telles que JsonIgnoreCondition.WhenWritingNull, pendant la sérialisation.

  • ApplicationRegion : Cette propriété est définie sur la région du cachet, ce qui permet au SDK de trouver le point de terminaison de la connexion le plus proche. Le point de terminaison doit de préférence se trouver dans la même région.

Le bloc de code suivant apparaît dans l'implémentation de référence :

//
// /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();

Messagerie asynchrone

Lorsque vous mettez en œuvre un couplage lâche, les services ne dépendent pas d'autres services. L'aspect lâche permet à un service de fonctionner de manière indépendante. L'aspect couplage permet la communication entre les services par le biais d'interfaces bien définies. Pour une application critique, le couplage lâche empêche les défaillances en aval de se répercuter sur les frontaux ou d'autres timbres de déploiement, ce qui assure une haute disponibilité.

Les principales caractéristiques de la messagerie asynchrone sont les suivantes :

  • Les services n'ont pas besoin d'utiliser la même plateforme informatique, le même langage de programmation ou le même système d'exploitation.

  • Les services sont mis à l’échelle indépendamment.

  • Les défaillances en aval n’affectent pas les transactions clientes.

  • L'intégrité transactionnelle est difficile à maintenir car la création et la persistance des données se font dans des services distincts. L'intégrité transactionnelle est un défi pour les services de messagerie et de persistance. Pour plus d'informations, reportez-vous à la section Traitement des messages idempotents.

  • Le traçage de bout en bout nécessite une orchestration complexe.

Nous vous recommandons d'utiliser des modèles de conception bien connus, tels que le modèle de nivellement de la charge basé sur les files d'attente et le modèle des consommateurs concurrents. Ces modèles répartissent la charge entre le producteur et les consommateurs et permettent un traitement asynchrone par les consommateurs. Par exemple, le travailleur permet à l'API d'accepter la requête et de retourner rapidement à l'appelant, et le travailleur traite séparément une opération d'écriture dans la base de données.

Event Hubs assure le courtage des messages entre l'API et le collaborateur.

Important

N'utilisez pas le courtier de messages comme un magasin de données persistant pendant de longues périodes. Le service Event Hubs prend en charge la fonctionnalité de capture. Cette fonctionnalité permet à un hub d'événements d'écrire automatiquement une copie des messages sur un compte de stockage lié. Ce processus contrôle l'utilisation et sert de mécanisme de sauvegarde des messages.

Détails de la mise en œuvre des opérations d'écriture

Les opérations d'écriture, telles que l'évaluation et le commentaire des messages, sont traitées de manière asynchrone. L'API envoie d'abord un message contenant toutes les informations pertinentes, telles que le type d'action et les données de commentaire, à la file d'attente des messages et renvoie immédiatement HTTP 202 (Accepted) avec l'en-tête Location de l'objet qui sera créé.

Les instances BackgroundProcessor traitent les messages dans la file d'attente et gèrent la communication avec la base de données pour les opérations d'écriture. BackgroundProcessorLe système de traitement des messages est mis à l'échelle de façon dynamique en fonction du volume de messages dans la file d'attente. La limite de scale-out des instances de processeur est définie par le nombre maximum de partitions Event Hubs, qui est de 32 pour les niveaux Basic et Standard, de 100 pour le niveau Premium et de 1 024 pour le niveau Dedicated.

Diagramme qui montre la nature asynchrone de la fonctionnalité de notation des points de contrôle dans la mise en œuvre.

La bibliothèque Azure Event Hubs Processor de BackgroundProcessor utilise Azure Blob Storage pour gérer la propriété des partitions, l'équilibrage de la charge entre les différentes instances de travailleurs et le suivi de la progression à l'aide de points de contrôle. Les points de contrôle ne sont pas écrits dans le stockage blob après chaque événement car cela ajoute un délai coûteux pour chaque message. Au lieu de cela, les points de contrôle sont écrits sur une boucle de temporisation dont vous pouvez configurer la durée. Le paramètre par défaut est 10 secondes.

Le bloc de code suivant apparaît dans l'implémentation de référence :

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 l'application du processeur rencontre une erreur ou est arrêtée avant de pouvoir traiter le message, une autre instance récupère le message pour le retraiter parce qu'il n'a pas été correctement pointé dans le stockage :

  • Une autre instance récupère le message pour le retraiter parce qu'il n'a pas été correctement contrôlé dans le stockage.

  • Un conflit se produit si le travailleur précédent a persisté le document dans la base de données avant que le travailleur n'échoue. Cette erreur se produit parce que le même ID et la même clé de partition sont utilisés. Le processeur peut ignorer le message en toute sécurité, car le document a déjà été transféré.

  • Une nouvelle instance répète les étapes et finalise la persistance si le travailleur précédent s'est arrêté avant d'écrire dans la base de données.

Détails de la mise en œuvre des opérations de lecture

L'API traite directement les opérations de lecture et renvoie immédiatement les données à l'utilisateur.

Le diagramme ci-dessous illustre le processus des opérations de lecture.

Aucune méthode de canal de retour n'est établie pour communiquer au client la réussite de l'opération. L'application cliente doit interroger l'API de manière proactive pour obtenir des mises à jour concernant l'élément spécifié dans l'en-tête HTTP Location.

Évolutivité

Les composants individuels de la charge de travail doivent être scale-out de manière indépendante car chaque composant a des modèles de charge différents. Les exigences de mise à l’échelle dépendent des fonctionnalités du service. Certains services affectent directement les utilisateurs et doivent être scale-out de manière agressive pour garantir des réponses rapides et une expérience positive pour l'utilisateur.

L'implémentation package les services sous forme d'images de conteneurs et utilise les diagrammes Helm pour déployer les services sur chaque cachet. Les services sont configurés pour avoir les requêtes et les limites Kubernetes attendues et une règle de mise à l'échelle automatique préconfigurée en place. Les composants CatalogService et BackgroundProcessor de la charge de travail peuvent être mis à l'échelle et réduits individuellement car les deux services sont sans état.

Les utilisateurs interagissent directement avec CatalogService. Cette partie de la charge de travail doit donc répondre sous n’importe quelle charge. Il y a un minimum de trois instances pour chaque cluster afin de les répartir sur trois zones de disponibilité dans une région Azure. L'autoscaler de pods horizontaux (HPA) dans AKS ajoute automatiquement des pods supplémentaires en fonction des besoins. La fonctionnalité Azure Cosmos DB autoscale peut augmenter et réduire dynamiquement les unités de requête (RU) disponibles pour la collecte. Le CatalogService et Azure Cosmos DB se combinent pour former une unité d'échelle à l'intérieur d'un timbre.

L'HPA est déployé avec un graphique Helm qui a un nombre maximum et un nombre minimum de répliques configurables. Le test de charge a déterminé que chaque instance peut gérer environ 250 requêtes par seconde avec un modèle d'utilisation standard.

Le service BackgroundProcessor a des exigences différentes et est considéré comme un travailleur en arrière-plan qui a un effet limité sur l'expérience de l'utilisateur. Ainsi, BackgroundProcessor a une configuration de mise à l'échelle automatique différente de CatalogService, et peut évoluer entre 2 et 32 instances. Déterminez cette limite en fonction du nombre de partitions que vous utilisez dans les hubs d'événements. Vous n'avez pas besoin de plus de travailleurs que de partitions.

Composant minReplicas maxReplicas
CatalogService 3 20
BackgroundProcessor 2 32

Chaque composant de la charge de travail qui inclut des dépendances comme ingress-nginx a le paramètre pod disruption budgets (PDBs) configuré pour garantir qu'un nombre minimum d'instances reste disponible lorsque les clusters changent.

Le bloc de code suivant apparaît dans l'implémentation de référence :

#
# /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 }}

Remarque

Déterminez le nombre minimum et maximum de modules pour chaque composant en effectuant des tests de charge. Le nombre de modules peut varier en fonction de la charge de travail.

Instrumentation

Utilisez l'instrumentation pour évaluer les goulots d'étranglement des performances et les problèmes de santé que les composants de la charge de travail peuvent introduire dans le système. Pour vous aider à quantifier les décisions, chaque composant doit émettre suffisamment d'informations par le biais de mesures et de journaux de suivi. Tenez compte des éléments clés suivants lorsque vous instrumentez votre application :

  • Envoyez les journaux, les mesures et autres télémétries au système de journaux du cachet.
  • Utilisez des journaux structurés plutôt que du texte brut afin de pouvoir interroger les informations.
  • Mettez en œuvre la corrélation des événements pour obtenir une vue de bout en bout de la transaction. Dans l'implémentation de référence, chaque réponse de l'API contient un ID de conteneur sous forme d'en-tête HTTP pour la traçabilité.
  • Ne vous fiez pas uniquement à la journalisation de la sortie standard (stdout) ou de la console. Mais vous pouvez utiliser ces journaux pour dépanner immédiatement un pod défaillant.

Cette architecture met en œuvre le traçage distribué avec Application Insights et un espace de travail Azure Monitor Logs pour les données de surveillance des applications. Utilisez Azure Monitor Logs pour les journaux et les mesures des charges de travail et des composants de l'infrastructure. Cette architecture met en œuvre un traçage complet de bout en bout des requêtes qui proviennent de l'API, passent par les hub d'événements, puis par Azure Cosmos DB.

Important

Déployez les ressources de surveillance des timbres dans un groupe de ressources de surveillance distinct. Les ressources ont un cycle de vie différent de celui du timbre lui-même. Pour plus d’informations, consultez Monitoring des données pour les ressources d’empreinte.

Diagramme des services globaux distincts, des services de surveillance et du déploiement des timbres.

Détails de la mise en œuvre de la surveillance des applications

Le composant BackgroundProcessor utilise le package NuGet Microsoft.ApplicationInsights.WorkerService pour obtenir une instrumentation prête à l’emploi à partir de l’application. Serilog est également utilisé pour tous les journaux à l'intérieur de l'application. Application Insights est configuré comme récepteur en plus de celui de la console. Une instance TelemetryClient pour Application Insights n'est utilisée directement que lorsqu'il est nécessaire de suivre d'autres mesures.

Le bloc de code suivant apparaît dans l'implémentation de référence :

//
// /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();
    }

Capture d’écran de la fonctionnalité de traçage de bout en bout.

Pour démontrer la traçabilité pratique des requêtes, chaque requête API réussie ou non renvoie l'en-tête Correlation ID à l'appelant. L'équipe de support de l'application peut effectuer une recherche dans Application Insights avec cet identifiant et obtenir un aperçu détaillé de la transaction complète, ce qui est illustré dans le diagramme précédent.

Le bloc de code suivant apparaît dans l'implémentation de référence :

//
// /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("X-Server-Location", sysConfig.AzureRegion);
            context.Response.Headers.Add("X-Correlation-ID", Activity.Current?.RootId);
            context.Response.Headers.Add("X-Requested-Api-Version", ctx.GetRequestedApiVersion()?.ToString());
        }
        return Task.CompletedTask;
    }, context);
    await next();
});

Remarque

L'échantillonnage adaptatif est activé par défaut dans le SDK Application Insights. L'échantillonnage adaptatif signifie que toutes les requêtes ne sont pas envoyées au cloud et ne peuvent pas faire l'objet d'une recherche par ID. Les équipes chargées des applications critiques ont besoin de tracer de manière fiable chaque requête, c'est pourquoi l'implémentation de référence a désactivé l'échantillonnage adaptatif dans l'environnement de production.

Détails de l’implémentation du monitoring Kubernetes

Vous pouvez utiliser les paramètres de diagnostic pour envoyer les journaux et les mesures d'AKS vers Azure Monitor Logs. Vous pouvez également utiliser la fonctionnalité d'aperçu des conteneurs avec AKS. Activez les aperçus de conteneurs pour déployer l'OMSAgentForLinux par le biais d'un DaemonSet Kubernetes sur chacun des nœuds des clusters AKS. L'OMSAgentForLinux peut collecter davantage de journaux et de mesures à partir du cluster Kubernetes et les envoyer à son espace de travail Azure Monitor Logs correspondant. Cet espace de travail contient des données granulaires sur les pods, les déploiements, les services et la santé globale du cluster.

Un journal extensif peut avoir un impact négatif sur les coûts et n'apporte pas d'avantages. Pour cette raison, la collecte de journaux stdout et le grattage Prometheus sont désactivés pour les pods de charge de travail dans la configuration de container insights, car toutes les traces sont déjà capturées via Application Insights, qui génère des enregistrements en double.

Le bloc de code suivant apparaît dans l'implémentation de référence :

#
# /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"]

Pour plus d'informations, consultez le fichier de configuration complet.

Surveillance de la santé des applications

Vous pouvez utiliser la surveillance et l'observabilité des applications pour identifier rapidement les problèmes système et informer le modèle de santé de l'état actuel de l'application. Vous pouvez mettre en surface la surveillance de la santé par le biais de points de terminaison de santé. Les sondes d'intégrité utilisent les données de surveillance de la santé pour fournir des informations. L'équilibreur de charge principal utilise ces informations pour retirer immédiatement le composant non sain de la rotation.

Cette architecture applique la surveillance de la santé aux niveaux suivants :

  • Les charges de travail qui s'exécutent sur AKS. Ces pods ont des sondes d'intégrité et liveness, de sorte qu'AKS peut gérer leur cycle de vie.

  • Service de santé, qui est un composant dédié sur le cluster. Azure Front Door est configuré pour sonder Health Service dans chaque timbre et supprimer les timbres non sains de l'équilibrage de charge automatique.

Détails de l'implémentation de Health Service

HealthService est un composant de charge de travail qui s'exécute avec d'autres composants, tels que CatalogService et BackgroundProcessor, sur le cluster de calcul. HealthServiceHealth Service fournit une API REST que le contrôle de santé d'Azure Front Door appelle pour déterminer la disponibilité d'un cachet. Contrairement aux sondes de disponibilité de base, Health Service est un composant plus complexe qui fournit l'état des dépendances en plus de son propre état.

Diagramme du service de santé interrogeant Azure Cosmos DB, Event Hubs et Storage.

Health Service ne répond pas si le cluster AKS est en panne, ce qui rend la charge de travail non saine. Lorsque le service s'exécute, il effectue des vérifications périodiques sur les composants critiques de la solution. Toutes les vérifications sont effectuées de manière asynchrone et en parallèle. Si l'un de ces contrôles échoue, l'ensemble du cachet est indisponible.

Avertissement

Les sondes d'intégrité Azure Front Door peuvent imposer une charge importante au service de santé, car les requêtes proviennent de plusieurs points de présence (PoP). Pour éviter de surcharger les composants en aval, mettez en place une mise en cache efficace.

Health Service est également utilisé pour les tests de ping d'URL explicitement configurés avec la ressource Application Insights de chaque cachet.

Pour plus d’informations sur l’implémentation de HealthService, consultez Service de contrôle d’intégrité des applications.

Étape suivante