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éthodeConfigureOrderedGroups
pour configurer les groupes ordonnés. - Ajoute un
EndpointGroup
à ceorderedGroup
qui achemine 3 % des demandes vers lehttps://example.net/api/experimental
point de terminaison et 97 % des demandes au point de terminaisonhttps://example.net/api/stable
. - Configure la
IRoutingStrategyBuilder
pour utiliser la méthodeConfigureWeightedGroups
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éthodeConfigureWeightedGroups
pour configurer les groupes pondérés. - Définit le
SelectionMode
surWeightedGroupSelectionMode.EveryAttempt
. - Ajoute un
WeightedEndpointGroup
à ceweightedGroup
qui achemine 33 % des demandes vers le point de terminaisonhttps://example.net/api/a
, 33% des requêtes au point de terminaisonhttps://example.net/api/b
et 33 % des demandes au point de terminaisonhttps://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 quepipelineName
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
etTooManyRequests
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 quepipelineName
au conteneur de service. - Active les rechargements du pipeline
"AdvancedPipeline"
chaque fois que les options nomméesRetryStrategyOptions
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 leExampleClient
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
:
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();