Partager via


Créer des applications HTTP résilientes : modèles de développement clés

La création d’applications HTTP robustes qui peuvent récupérer à partir d’erreurs d’erreur temporaires est une exigence courante. Cet article suppose que vous avez déjà lu Introduction au développement d’applications résilientes, car cet article étend les concepts fondamentaux transmis. Pour aider à créer des applications HTTP résilientes, le package NuGet Microsoft.Extensions.Http.Resilience fournit des mécanismes de résilience spécifiquement pour le HttpClient. Ce package NuGet s’appuie sur la bibliothèque Microsoft.Extensions.Resilience et Polly, qui est un projet open source populaire. Pour plus d’informations, consultez Polly.

Bien démarrer

Pour utiliser des modèles de résilience dans les applications HTTP, installez le package NuGet Microsoft.Extensions.Http.Resilience.

dotnet add package Microsoft.Extensions.Http.Resilience --version 8.0.0

Pour plus d’informations, consultez dotnet ajouter package ou Gérer les dépendances de package dans les applications .NET.

Ajouter une résilience à un client HTTP

Pour ajouter la résilience à un HttpClient, vous chaînez un appel sur le IHttpClientBuilder type retourné par l’appel de l’une des méthodes AddHttpClient disponibles. Pour plus d’informations, consultez IHttpClientFactory avec .NET.

Plusieurs extensions centrées sur la résilience sont disponibles. Certains sont standard, employant ainsi diverses meilleures pratiques du secteur, et d’autres sont plus personnalisables. Lors de l’ajout de la résilience, vous ne devez ajouter qu’un seul gestionnaire de résilience et éviter les gestionnaires de pile. Si vous devez ajouter plusieurs gestionnaires de résilience, vous devez envisager d’utiliser la méthode d’extension AddResilienceHandler, ce qui vous permet de personnaliser les stratégies de résilience.

Important

Tous les exemples de cet article s’appuient sur l’API AddHttpClient, à partir de la bibliothèque Microsoft.Extensions.Http, qui renvoie une instance IHttpClientBuilder. L’instance IHttpClientBuilder est utilisée pour configurer le HttpClient et ajouter le gestionnaire de résilience.

Ajouter un gestionnaire de résilience standard

Le gestionnaire de résilience standard utilise plusieurs stratégies de résilience empilées les unes sur les autres, avec des options par défaut pour envoyer les requêtes et gérer les erreurs temporaires. Le gestionnaire de résilience standard est ajouté en appelant la méthode AddStandardResilienceHandler d’extension sur une instance IHttpClientBuilder.

var services = new ServiceCollection();

var httpClientBuilder = services.AddHttpClient<ExampleClient>(
    configureClient: static client =>
    {
        client.BaseAddress = new("https://jsonplaceholder.typicode.com");
    });

Le code précédent :

  • Crée une instance de ServiceCollection.
  • Ajoute un HttpClientpour le type ExampleClient au conteneur de service.
  • Configure le HttpClient pour l’utilisation de "https://jsonplaceholder.typicode.com" comme adresse de base.
  • Crée le httpClientBuilder utilisé dans les autres exemples de cet article.

Un exemple plus réel s’appuie sur l’hébergement, tel que décrit dans l’article sur l’hôte générique .NET. À l’aide du package NuGet Microsoft.Extensions.Hosting , tenez compte de l’exemple mis à jour suivant :

using Http.Resilience.Example;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

IHttpClientBuilder httpClientBuilder = builder.Services.AddHttpClient<ExampleClient>(
    configureClient: static client =>
    {
        client.BaseAddress = new("https://jsonplaceholder.typicode.com");
    });

Le code précédent est similaire à l’approche de création manuelle ServiceCollection, mais s’appuie plutôt sur la Host.CreateApplicationBuilder() pour générer un hôte qui expose les services.

Le ExampleClient est défini comme suit :

using System.Net.Http.Json;

namespace Http.Resilience.Example;

/// <summary>
/// An example client service, that relies on the <see cref="HttpClient"/> instance.
/// </summary>
/// <param name="client">The given <see cref="HttpClient"/> instance.</param>
internal sealed class ExampleClient(HttpClient client)
{
    /// <summary>
    /// Returns an <see cref="IAsyncEnumerable{T}"/> of <see cref="Comment"/>s.
    /// </summary>
    public IAsyncEnumerable<Comment?> GetCommentsAsync()
    {
        return client.GetFromJsonAsAsyncEnumerable<Comment>("/comments");
    }
}

Le code précédent :

  • Définit un type ExampleClient qui a un constructeur qui accepte un HttpClient.
  • Expose une méthode GetCommentsAsync qui envoie une requête GET au point de terminaison /comments et retourne la réponse.

Le type Comment est défini comme suit :

namespace Http.Resilience.Example;

public record class Comment(
    int PostId, int Id, string Name, string Email, string Body);

Étant donné que vous avez créé un IHttpClientBuilder (httpClientBuilder) et que vous comprenez maintenant l’implémentation ExampleClient et le modèle Comment correspondant, considérez l’exemple suivant :

httpClientBuilder.AddStandardResilienceHandler();

Le code précédent ajoute le gestionnaire de résilience standard au HttpClient. Comme la plupart des API de résilience, il existe des surcharges qui vous permettent de personnaliser les options par défaut et les stratégies de résilience appliquées.

Gestionnaire de résilience standard par défaut

La configuration par défaut chaîne cinq stratégies de résilience dans l’ordre suivant (de l’extérieur au plus profond) :

Commande Stratégie Description Valeurs par défaut
1 Limiteur de débit Le pipeline limite le nombre maximal de requêtes simultanées envoyées à la dépendance. File d’attente : 0
Autoriser : 1_000
2 Délai d’expiration total Le pipeline total de délai d’expiration de la requête applique un délai d’expiration global à l’exécution, ce qui garantit que la requête, y compris les tentatives de nouvelle tentative, ne dépasse pas la limite configurée. Délai d’expiration total : 30 s
3 Réessayer Le pipeline de nouvelle tentative retente la requête au cas où la dépendance était lente ou retourne une erreur temporaire. Nombre maximal de nouvelles tentatives : 3
Interruption : Exponential
Utiliser le jitter : true
Retard :2 s
4 Disjoncteur Le disjoncteur bloque l’exécution si trop de défaillances directes ou de délais d’attente sont détectés. Taux de défaillance : 10 %
Débit minimal : 100
Durée d’échantillonnage : 30 s
Durée d’interruption : 5 s
5 Délai d’expiration de la tentative Le pipeline de délai d’expiration de la tentative limite chaque durée de la tentative de requête et lève s’il est dépassé. Délai d’expiration de la tentative : 10 s

Nouvelles tentatives et disjoncteurs

Les stratégies de nouvelle tentative et de disjoncteurs gèrent toutes deux un ensemble de codes d’état HTTP spécifiques et des exceptions. Tenez compte des codes d’état HTTP suivants :

  • HTTP 500 et ultérieurs (erreurs de serveur)
  • HTTP 408 (Délai d'expiration total de la requête)
  • HTTP 429 (Trop de requêtes)

En outre, ces stratégies gèrent les exceptions suivantes :

  • HttpRequestException
  • TimeoutRejectedException

Ajouter un gestionnaire de couverture standard

Le gestionnaire de couverture standard encapsule l’exécution de la requête avec un mécanisme de couverture standard. La création de nouvelles tentatives ralentit les requêtes en parallèle.

Pour utiliser le gestionnaire de couverture standard, appelez la méthode d’extension AddStandardHedgingHandler. L’exemple suivant configure ExampleClient pour utiliser le gestionnaire de couverture standard.

httpClientBuilder.AddStandardHedgingHandler();

Le code précédent ajoute le gestionnaire de couverture standard au HttpClient.

Gestionnaire de couverture standard par défaut

La couverture standard utilise un pool de disjoncteurs pour s’assurer que les points de terminaison non sains ne sont pas couverts. Par défaut, la sélection à partir du pool est basée sur l’autorité d’URL (schéma + hôte + port).

Conseil

Il est recommandé de configurer la façon dont les stratégies sont sélectionnées en appelant StandardHedgingHandlerBuilderExtensions.SelectPipelineByAuthority ou StandardHedgingHandlerBuilderExtensions.SelectPipelineBy pour des scénarios plus avancés.

Le code précédent ajoute le gestionnaire de couverture standard au IHttpClientBuilder. La configuration par défaut chaîne cinq stratégies de résilience dans l’ordre suivant (de l’extérieur au plus profond) :

Commande Stratégie Description Valeurs par défaut
1 Délai d'expiration total de la requête Le pipeline total de délai d’expiration de la requête applique un délai d’expiration global à l’exécution, ce qui garantit que la requête, y compris les tentatives de couverture, ne dépasse pas la limite configurée. Délai d’expiration total : 30 s
2 Couverture La stratégie de couverture exécute les requêtes sur plusieurs points de terminaison au cas où la dépendance était lente ou retourne une erreur temporaire. Le routage est des options, par défaut, il couvre simplement l’URL fournie par l’original HttpRequestMessage. Nombre de tentatives minimales : 1
Nombre de tentatives maximales : 10
Retard : 2 s
3 Limiteur de débit (par point de terminaison) Le pipeline limite le nombre maximal de requêtes simultanées envoyées à la dépendance. File d’attente : 0
Autoriser : 1_000
4 Disjoncteur (par point de terminaison) Le disjoncteur bloque l’exécution si trop de défaillances directes ou de délais d’attente sont détectés. Taux de défaillance : 10 %
Débit minimal : 100
Durée d’échantillonnage : 30 s
Durée d’interruption : 5 s
5 Délai d’expiration de la tentative (par point de terminaison) Le pipeline de délai d’expiration de la tentative limite chaque durée de la tentative de requête et lève s’il est dépassé. Délai d’expiration : 10 s

Personnaliser la sélection de l’itinéraire du gestionnaire de couverture

Lorsque vous utilisez le gestionnaire de couverture standard, vous pouvez personnaliser la façon dont les points de terminaison de requête sont sélectionnés en appelant différentes extensions sur le IRoutingStrategyBuilder type. Cela peut être utile pour les scénarios tels que les tests A/B, où vous souhaitez router un pourcentage des requêtes vers un autre point de terminaison :

httpClientBuilder.AddStandardHedgingHandler(static (IRoutingStrategyBuilder builder) =>
{
    // Hedging allows sending multiple concurrent requests
    builder.ConfigureOrderedGroups(static options =>
    {
        options.Groups.Add(new UriEndpointGroup()
        {
            Endpoints =
            {
                // Imagine a scenario where 3% of the requests are 
                // sent to the experimental endpoint.
                new() { Uri = new("https://example.net/api/experimental"), Weight = 3 },
                new() { Uri = new("https://example.net/api/stable"), Weight = 97 }
            }
        });
    });
});

Le code précédent :

  • Ajoute le gestionnaire de couverture au IHttpClientBuilder.
  • Configure la IRoutingStrategyBuilder pour utiliser la méthode ConfigureOrderedGroups pour configurer les groupes ordonnés.
  • Ajoute un EndpointGroup à ce orderedGroup qui achemine 3 % des demandes vers le https://example.net/api/experimental point de terminaison et 97 % des demandes au point de terminaison https://example.net/api/stable.
  • Configure la IRoutingStrategyBuilder pour utiliser la méthode ConfigureWeightedGroups pour configurer les

Pour configurer un groupe pondéré, appelez la méthode ConfigureWeightedGroups sur le type IRoutingStrategyBuilder. L’exemple suivant configure le IRoutingStrategyBuilder pour utiliser la méthode ConfigureWeightedGroups pour configurer les groupes pondérés.

httpClientBuilder.AddStandardHedgingHandler(static (IRoutingStrategyBuilder builder) =>
{
    // Hedging allows sending multiple concurrent requests
    builder.ConfigureWeightedGroups(static options =>
    {
        options.SelectionMode = WeightedGroupSelectionMode.EveryAttempt;

        options.Groups.Add(new WeightedUriEndpointGroup()
        {
            Endpoints =
            {
                // Imagine A/B testing
                new() { Uri = new("https://example.net/api/a"), Weight = 33 },
                new() { Uri = new("https://example.net/api/b"), Weight = 33 },
                new() { Uri = new("https://example.net/api/c"), Weight = 33 }
            }
        });
    });
});

Le code précédent :

  • Ajoute le gestionnaire de couverture au IHttpClientBuilder.
  • Configure la IRoutingStrategyBuilder pour utiliser la méthode ConfigureWeightedGroups pour configurer les groupes pondérés.
  • Définit le SelectionMode sur WeightedGroupSelectionMode.EveryAttempt.
  • Ajoute un WeightedEndpointGroup à ce weightedGroup qui achemine 33 % des demandes vers le point de terminaison https://example.net/api/a, 33% des requêtes au point de terminaison https://example.net/api/b et 33 % des demandes au point de terminaison https://example.net/api/c.

Conseil

Le nombre maximal de tentatives de couverture est directement corrélé au nombre de groupes configurés. Par exemple, si vous avez deux groupes, le nombre maximal de tentatives est de deux.

Pour plus d’informations, consultez Documentation Polly : Stratégie de résilience de couverture.

Il est courant de configurer un groupe ordonné ou un groupe pondéré, mais il est valide pour configurer les deux. L’utilisation de groupes ordonnés et pondérés est utile dans les scénarios où vous souhaitez envoyer un pourcentage des demandes à un autre point de terminaison, par exemple avec des tests A/B.

Ajouter des gestionnaires de résilience personnalisés

Pour avoir plus de contrôle, vous pouvez personnaliser les gestionnaires de résilience à l’aide de l’API AddResilienceHandler. Cette méthode accepte un délégué qui configure l’instance ResiliencePipelineBuilder<HttpResponseMessage> utilisée pour créer les stratégies de résilience.

Pour configurer un gestionnaire de résilience nommé, appelez la méthode d’extension AddResilienceHandler avec le nom du gestionnaire. L’exemple suivant configure un gestionnaire de résilience nommé appelé "CustomPipeline".

httpClientBuilder.AddResilienceHandler(
    "CustomPipeline",
    static builder =>
{
    // See: https://www.pollydocs.org/strategies/retry.html
    builder.AddRetry(new HttpRetryStrategyOptions
    {
        // Customize and configure the retry logic.
        BackoffType = DelayBackoffType.Exponential,
        MaxRetryAttempts = 5,
        UseJitter = true
    });

    // See: https://www.pollydocs.org/strategies/circuit-breaker.html
    builder.AddCircuitBreaker(new HttpCircuitBreakerStrategyOptions
    {
        // Customize and configure the circuit breaker logic.
        SamplingDuration = TimeSpan.FromSeconds(10),
        FailureRatio = 0.2,
        MinimumThroughput = 3,
        ShouldHandle = static args =>
        {
            return ValueTask.FromResult(args is
            {
                Outcome.Result.StatusCode:
                    HttpStatusCode.RequestTimeout or
                        HttpStatusCode.TooManyRequests
            });
        }
    });

    // See: https://www.pollydocs.org/strategies/timeout.html
    builder.AddTimeout(TimeSpan.FromSeconds(5));
});

Le code précédent :

  • Ajoute un gestionnaire de résilience portant le nom "CustomPipeline" en tant que pipelineName au conteneur de service.
  • Ajoute une stratégie de nouvelle tentative avec une interruption exponentielle, cinq nouvelles tentatives et une préférence de gigue au générateur de résilience.
  • Ajoute une stratégie de disjoncteur avec une durée d’échantillonnage de 10 secondes, un ratio d’échec de 0,2 (20 %), un débit minimal de trois et un prédicat qui gère des codes d’état HTTP RequestTimeout et TooManyRequests au générateur de résilience.
  • Ajoute une stratégie de délai d’expiration avec un délai d’expiration de cinq secondes au générateur de résilience.

Il existe de nombreuses options disponibles pour chacune des stratégies de résilience. Pour plus d’informations, consultez la Documentation Polly : Stratégies. Pour plus d’informations sur la configuration des délégués ShouldHandle, consultez documentation Polly : Gestion des pannes dans les stratégies réactives.

Rechargement dynamique

Polly prend en charge le rechargement dynamique des stratégies de résilience configurées. Cela signifie que vous pouvez modifier la configuration des stratégies de résilience au moment de l’exécution. Pour activer le rechargement dynamique, utilisez la surcharge AddResilienceHandler appropriée qui expose le ResilienceHandlerContext. Étant donné le contexte, appelez EnableReloads les options de stratégie de résilience correspondantes :

httpClientBuilder.AddResilienceHandler(
    "AdvancedPipeline",
    static (ResiliencePipelineBuilder<HttpResponseMessage> builder,
        ResilienceHandlerContext context) =>
    {
        // Enable reloads whenever the named options change
        context.EnableReloads<HttpRetryStrategyOptions>("RetryOptions");

        // Retrieve the named options
        var retryOptions =
            context.GetOptions<HttpRetryStrategyOptions>("RetryOptions");

        // Add retries using the resolved options
        builder.AddRetry(retryOptions);
    });

Le code précédent :

  • Ajoute un gestionnaire de résilience portant le nom "AdvancedPipeline" en tant que pipelineName au conteneur de service.
  • Active les rechargements du pipeline "AdvancedPipeline" chaque fois que les options nommées RetryStrategyOptions changent.
  • Récupère les options nommées du service IOptionsMonitor<TOptions>.
  • Ajoute une stratégie de nouvelle tentative avec les options récupérées au générateur de résilience.

Pour plus d’informations, consultez Documentation Polly : Injection de dépendances avancée.

Cet exemple s’appuie sur une section d’options capable de changer, telle qu’un fichier appsettings.json. Considérez le fichier appsettings.json suivant :

{
    "RetryOptions": {
        "Retry": {
            "BackoffType": "Linear",
            "UseJitter": false,
            "MaxRetryAttempts": 7
        }
    }
}

Imaginez maintenant que ces options étaient liées à la configuration de l’application, en liant la HttpRetryStrategyOptions à la section "RetryOptions" :

var section = builder.Configuration.GetSection("RetryOptions");

builder.Services.Configure<HttpStandardResilienceOptions>(section);

Pour plus d’informations, consultez Modèle d’options dans .NET.

Exemple d’utilisation

Votre application s’appuie sur l’injection de dépendances pour résoudre le ExampleClient et son HttpClient correspondant. Le code génère le IServiceProvider et résout le ExampleClient qui en découle.

IHost host = builder.Build();

ExampleClient client = host.Services.GetRequiredService<ExampleClient>();

await foreach (Comment? comment in client.GetCommentsAsync())
{
    Console.WriteLine(comment);
}

Le code précédent :

  • Génère l’élément IServiceProvider à partir du ServiceCollection.
  • Résout la ExampleClient du IServiceProvider.
  • Appelle la méthode GetCommentsAsync sur le ExampleClient obtenir les commentaires.
  • Écrit chaque commentaire dans la console.

Imaginez une situation où le réseau tombe en panne ou que le serveur ne répond pas. Le diagramme suivant montre comment les stratégies de résilience géreraient la situation, en fonction de la méthode ExampleClient et de la méthode GetCommentsAsync :

Exemple de flux de travail HTTP GET avec pipeline de résilience.

Le diagramme précédent représente :

  • Le ExampleClient envoie une requête HTTP GET au point de terminaison /comments.
  • L’élément HttpResponseMessage est évalué :
    • Si la réponse réussit (HTTP 200), la réponse est renvoyée.
    • Si la réponse échoue (HTTP non-200), le pipeline de résilience utilise les stratégies de résilience configurées.

Bien qu’il s’agit d’un exemple simple, il montre comment les stratégies de résilience peuvent être utilisées pour gérer les erreurs temporaires. Pour plus d’informations, consultez Documentation Polly : Stratégies.

Problèmes connus

Les sections suivantes détaillent divers problèmes connus.

Compatibilité avec le Grpc.Net.ClientFactory package

Si vous utilisez Grpc.Net.ClientFactory version 2.63.0 ou une version antérieure, alors l’activation des gestionnaires de résilience standard ou de hedging pour un client gRPC pourrait provoquer une exception d’exécution. Plus précisément, considérez l’exemple de code suivant :

services
    .AddGrpcClient<Greeter.GreeterClient>()
    .AddStandardResilienceHandler();

Le code précédent génère l’exception suivante :

System.InvalidOperationException: The ConfigureHttpClient method is not supported when creating gRPC clients. Unable to create client with name 'GreeterClient'.

Pour résoudre ce problème, nous recommandons de passer à Grpc.Net.ClientFactory version 2.64.0 ou une version ultérieure.

Il existe une vérification au moment de la compilation qui vérifie si vous utilisez Grpc.Net.ClientFactory version 2.63.0 ou une version antérieure, et si c’est le cas, la vérification produit un avertissement de compilation. Vous pouvez supprimer cet avertissement en définissant la propriété suivante dans votre fichier projet :

<PropertyGroup>
  <SuppressCheckGrpcNetClientFactoryVersion>true</SuppressCheckGrpcNetClientFactoryVersion>
</PropertyGroup>

Compatibilité avec .NET Application Insights

Si vous utilisez .NET Application Insights, l'activation de la fonctionnalité de résilience dans votre application peut entraîner l'absence de toute télémétrie Application Insights. Le problème se produit lorsque la fonctionnalité de résilience est enregistrée avant les services Application Insights. Considérez que l'exemple suivant est à l'origine du problème :

// At first, we register resilience functionality.
services.AddHttpClient().AddStandardResilienceHandler();

// And then we register Application Insights. As a result, Application Insights doesn't work.
services.AddApplicationInsightsTelemetry();

Le problème est causé par le bogue suivant dans Application Insights et peut être corrigé en enregistrant les services Application Insights avant la fonctionnalité de résilience, comme indiqué ci-dessous :

// We register Application Insights first, and now it will be working correctly.
services.AddApplicationInsightsTelemetry();
services.AddHttpClient().AddStandardResilienceHandler();