Tolerante HTTP-apps bouwen: Belangrijke ontwikkelingspatronen
Het bouwen van robuuste HTTP-apps die kunnen worden hersteld na tijdelijke foutfouten is een veelvoorkomende vereiste. In dit artikel wordt ervan uitgegaan dat u inleiding tot tolerante app-ontwikkeling al hebt gelezen, omdat in dit artikel de belangrijkste concepten worden uitgebreid. Om tolerante HTTP-apps te bouwen, biedt het NuGet-pakket Microsoft.Extensions.Http.Resilience tolerantiemechanismen specifiek voor de HttpClient. Dit NuGet-pakket is afhankelijk van de Microsoft.Extensions.Resilience
bibliotheek en Polly, een populair opensource-project. Zie Polly voor meer informatie.
Aan de slag
Als u tolerantiepatronen in HTTP-apps wilt gebruiken, installeert u het NuGet-pakket Microsoft.Extensions.Http.Resilience .
dotnet add package Microsoft.Extensions.Http.Resilience --version 8.0.0
Zie dotnet-pakket toevoegen of pakketafhankelijkheden beheren in .NET-toepassingen voor meer informatie.
Tolerantie toevoegen aan een HTTP-client
Als u tolerantie wilt toevoegen aan een HttpClient, koppelt u een aanroep van het IHttpClientBuilder type dat wordt geretourneerd door het aanroepen van een van de beschikbare AddHttpClient methoden. Zie IHttpClientFactory met .NET voor meer informatie.
Er zijn verschillende tolerantiegerichte extensies beschikbaar. Sommige zijn standaard, waardoor verschillende aanbevolen procedures voor de branche worden gebruikt, en andere zijn beter aanpasbaar. Wanneer u tolerantie toevoegt, moet u slechts één tolerantiehandler toevoegen en stackinghandlers voorkomen. Als u meerdere handlers voor tolerantie moet toevoegen, moet u overwegen de AddResilienceHandler
extensiemethode te gebruiken, zodat u de strategieën voor tolerantie kunt aanpassen.
Belangrijk
Alle voorbeelden in dit artikel zijn afhankelijk van de AddHttpClient API, uit de bibliotheek Microsoft.Extensions.Http , die een IHttpClientBuilder exemplaar retourneert. Het IHttpClientBuilder exemplaar wordt gebruikt om de HttpClient tolerantiehandler te configureren en toe te voegen.
Standaardhandler voor tolerantie toevoegen
De standaardtolerantiehandler maakt gebruik van meerdere strategieën voor tolerantie die op elkaar zijn gestapeld, met standaardopties voor het verzenden van aanvragen en het afhandelen van tijdelijke fouten. De standaardtolerantiehandler wordt toegevoegd door de AddStandardResilienceHandler
extensiemethode op een IHttpClientBuilder exemplaar aan te roepen.
var services = new ServiceCollection();
var httpClientBuilder = services.AddHttpClient<ExampleClient>(
configureClient: static client =>
{
client.BaseAddress = new("https://jsonplaceholder.typicode.com");
});
Met de voorgaande code wordt:
- Hiermee maakt u een ServiceCollection exemplaar.
- Hiermee voegt u een HttpClient voor het
ExampleClient
type toe aan de servicecontainer. - Hiermee configureert u het HttpClient te gebruiken
"https://jsonplaceholder.typicode.com"
als het basisadres. - Hiermee maakt u de
httpClientBuilder
gegevens die in de andere voorbeelden in dit artikel worden gebruikt.
Een praktijkvoorbeeld is afhankelijk van hosting, zoals die wordt beschreven in het artikel .NET Generic Host . Bekijk het volgende bijgewerkte voorbeeld met behulp van het NuGet-pakket Microsoft.Extensions.Hosting :
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");
});
De voorgaande code is vergelijkbaar met de benadering voor handmatig ServiceCollection
maken, maar is in plaats daarvan afhankelijk van het Host.CreateApplicationBuilder() bouwen van een host die de services beschikbaar maakt.
De ExampleClient
definitie is als volgt:
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");
}
}
Met de voorgaande code wordt:
- Definieert een
ExampleClient
type dat een constructor heeft die een HttpClient. - Hiermee wordt een
GetCommentsAsync
methode weergegeven waarmee een GET-aanvraag naar het/comments
eindpunt wordt verzonden en het antwoord wordt geretourneerd.
Het Comment
type wordt als volgt gedefinieerd:
namespace Http.Resilience.Example;
public record class Comment(
int PostId, int Id, string Name, string Email, string Body);
Gezien het feit dat u een IHttpClientBuilder (httpClientBuilder
) hebt gemaakt en u nu de implementatie en het ExampleClient
bijbehorende Comment
model begrijpt, kunt u het volgende voorbeeld overwegen:
httpClientBuilder.AddStandardResilienceHandler();
Met de voorgaande code wordt de standaard-tolerantiehandler toegevoegd aan de HttpClient. Net als bij de meeste tolerantie-API's zijn er overbelastingen waarmee u de standaardopties en toegepaste tolerantiestrategieën kunt aanpassen.
Standaardinstellingen voor tolerantiehandler
De standaardconfiguratie koppelt vijf tolerantiestrategieën in de volgende volgorde (van buitenste naar binnenste):
Order | Strategie | Beschrijving | Defaults |
---|---|---|---|
1 | Snelheidsbegrenzer | De pijplijn voor frequentielimiet beperkt het maximum aantal gelijktijdige aanvragen dat naar de afhankelijkheid wordt verzonden. | Rij: 0 Toestaan: 1_000 |
2 | Totale time-out | De totale time-outpijplijn voor aanvragen past een algemene time-out toe op de uitvoering, zodat de aanvraag, inclusief nieuwe pogingen, de geconfigureerde limiet niet overschrijdt. | Totale time-out: 30s |
3 | Opnieuw proberen | De pijplijn voor opnieuw proberen probeert de aanvraag opnieuw uit te voeren voor het geval de afhankelijkheid traag is of een tijdelijke fout retourneert. | Maximum aantal nieuwe pogingen: 3 Uitstel: Exponential Jitter gebruiken: true Vertraging:2s |
4 | Stroomonderbreker | De circuitonderbreker blokkeert de uitvoering als er te veel directe fouten of time-outs worden gedetecteerd. | Foutverhouding: 10% Minimale doorvoer: 100 Steekproefduur: 30s Duur pauze: 5s |
5 | Time-out van poging | De time-outpijplijn voor pogingen beperkt elke duur van de aanvraagpoging en genereert als deze wordt overschreden. | Time-out voor poging: 10s |
Nieuwe pogingen en circuitonderbrekers
De strategieën voor opnieuw proberen en circuitonderbrekers verwerken beide een set specifieke HTTP-statuscodes en uitzonderingen. Houd rekening met de volgende HTTP-statuscodes:
- HTTP 500 en hoger (serverfouten)
- HTTP 408 (time-out aanvraag)
- HTTP 429 (te veel aanvragen)
Daarnaast verwerken deze strategieën de volgende uitzonderingen:
HttpRequestException
TimeoutRejectedException
Standaardhandler voor hedging toevoegen
De standaardhandler voor hedging verpakt de uitvoering van de aanvraag met een standaard hedgingsmechanisme. Het opnieuw afhandelen van nieuwe pogingen voor trage aanvragen parallel.
Als u de standaardhandler voor hedging wilt gebruiken, roept u AddStandardHedgingHandler
de extensiemethode aan. In het volgende voorbeeld wordt het configureren van de ExampleClient
standaardhandler voor hedging.
httpClientBuilder.AddStandardHedgingHandler();
Met de voorgaande code wordt de standaardhandler voor hedging toegevoegd aan de HttpClient.
Standaardinstellingen voor hedginghandler
De standaard hedging maakt gebruik van een groep circuitonderbrekers om ervoor te zorgen dat beschadigde eindpunten niet worden afgedekt tegen. De selectie uit de pool is standaard gebaseerd op de URL-instantie (schema + host + poort).
Tip
Het wordt aanbevolen om de manier te configureren waarop de strategieën worden geselecteerd door aan te roepen StandardHedgingHandlerBuilderExtensions.SelectPipelineByAuthority
of StandardHedgingHandlerBuilderExtensions.SelectPipelineBy
voor meer geavanceerde scenario's.
Met de voorgaande code wordt de standaardhandler voor hedging toegevoegd aan de IHttpClientBuilder. De standaardconfiguratie koppelt vijf tolerantiestrategieën in de volgende volgorde (van buitenste naar binnenste):
Order | Strategie | Beschrijving | Defaults |
---|---|---|---|
1 | Totale time-out van aanvraag | De totale time-outpijplijn voor aanvragen past een algemene time-out toe op de uitvoering, zodat de aanvraag, inclusief hedgingspogingen, de geconfigureerde limiet niet overschrijdt. | Totale time-out: 30s |
2 | Hedging | De hedgingsstrategie voert de aanvragen uit op meerdere eindpunten als de afhankelijkheid traag is of een tijdelijke fout retourneert. Routering is opties, standaard wordt de URL van het origineel HttpRequestMessageafgedekt. | Minimale pogingen: 1 Maximum aantal pogingen: 10 Vertraging: 2s |
3 | Snelheidsbegrenzer (per eindpunt) | De pijplijn voor frequentielimiet beperkt het maximum aantal gelijktijdige aanvragen dat naar de afhankelijkheid wordt verzonden. | Rij: 0 Toestaan: 1_000 |
4 | Circuitonderbreker (per eindpunt) | De circuitonderbreker blokkeert de uitvoering als er te veel directe fouten of time-outs worden gedetecteerd. | Foutverhouding: 10% Minimale doorvoer: 100 Steekproefduur: 30s Duur pauze: 5s |
5 | Time-outpoging (per eindpunt) | De time-outpijplijn voor pogingen beperkt elke duur van de aanvraagpoging en genereert als deze wordt overschreden. | Time-out: 10s |
Routeselectie voor hedgingshandler aanpassen
Wanneer u de standaardhandler voor hedging gebruikt, kunt u de manier aanpassen waarop de aanvraageindpunten worden geselecteerd door verschillende extensies voor het IRoutingStrategyBuilder
type aan te roepen. Dit kan handig zijn voor scenario's zoals A/B-tests, waarbij u een percentage van de aanvragen naar een ander eindpunt wilt routeren:
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 }
}
});
});
});
Met de voorgaande code wordt:
- Voegt de hedging-handler toe aan de IHttpClientBuilder.
- Hiermee configureert u de
IRoutingStrategyBuilder
ConfigureOrderedGroups
methode om de geordende groepen te configureren. - Hiermee wordt een
EndpointGroup
aan deorderedGroup
aanvraag toegevoegd waarmee 3% van de aanvragen naar hethttps://example.net/api/experimental
eindpunt wordt gerouteerd en 97% van de aanvragen naar hethttps://example.net/api/stable
eindpunt. - Hiermee configureert u de
IRoutingStrategyBuilder
methode voor hetConfigureWeightedGroups
configureren van de
Als u een gewogen groep wilt configureren, roept u de ConfigureWeightedGroups
methode voor het IRoutingStrategyBuilder
type aan. In het volgende voorbeeld wordt de IRoutingStrategyBuilder
methode geconfigureerd voor ConfigureWeightedGroups
het configureren van de gewogen groepen.
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 }
}
});
});
});
Met de voorgaande code wordt:
- Voegt de hedging-handler toe aan de IHttpClientBuilder.
- Hiermee configureert u de
IRoutingStrategyBuilder
ConfigureWeightedGroups
methode om de gewogen groepen te configureren. - Hiermee stelt u het in
SelectionMode
opWeightedGroupSelectionMode.EveryAttempt
. - Hiermee wordt een
WeightedEndpointGroup
aan deweightedGroup
aanvraag toegevoegd waarmee 33% van de aanvragen naar hethttps://example.net/api/a
eindpunt wordt gerouteerd, 33% van de aanvragen naar hethttps://example.net/api/b
eindpunt en 33% van de aanvragen naar hethttps://example.net/api/c
eindpunt.
Tip
Het maximum aantal hedgingspogingen komt rechtstreeks overeen met het aantal geconfigureerde groepen. Als u bijvoorbeeld twee groepen hebt, is het maximum aantal pogingen twee.
Zie Voor meer informatie Polly docs: Hedging resilience strategy.
Het is gebruikelijk om een geordende groep of gewogen groep te configureren, maar het is geldig om beide te configureren. Het gebruik van geordende en gewogen groepen is handig in scenario's waarin u een percentage van de aanvragen naar een ander eindpunt wilt verzenden. Dit is het geval bij A/B-tests.
Aangepaste tolerantiehandlers toevoegen
Als u meer controle wilt hebben, kunt u de tolerantiehandlers aanpassen met behulp van de AddResilienceHandler
API. Deze methode accepteert een gemachtigde die het ResiliencePipelineBuilder<HttpResponseMessage>
exemplaar configureert dat wordt gebruikt om de strategieën voor tolerantie te maken.
Als u een benoemde tolerantiehandler wilt configureren, roept u de AddResilienceHandler
extensiemethode aan met de naam van de handler. In het volgende voorbeeld wordt een benoemde tolerantiehandler geconfigureerd met de naam "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));
});
Met de voorgaande code wordt:
- Voegt een tolerantiehandler toe met de naam als de
pipelineName
servicecontainer"CustomPipeline"
. - Voegt een strategie voor opnieuw proberen toe met exponentieel uitstel, vijf nieuwe pogingen en jitter-voorkeur voor de opbouwfunctie voor tolerantie.
- Voegt een circuitonderbrekerstrategie toe met een steekproefduur van 10 seconden, een foutverhouding van 0,2 (20%), een minimale doorvoer van drie en een predicaat dat http-statuscodes verwerkt
RequestTimeout
enTooManyRequests
HTTP-statuscodes aan de opbouwfunctie voor tolerantie. - Hiermee voegt u een time-outstrategie toe met een time-out van vijf seconden aan de opbouwfunctie voor tolerantie.
Er zijn veel opties beschikbaar voor elk van de tolerantiestrategieën. Zie de Documenten van Polly: Strategieën voor meer informatie. Zie Polly-documenten voor meer informatie over het configureren van ShouldHandle
gemachtigden : Foutafhandeling in reactieve strategieën.
Dynamisch opnieuw laden
Polly biedt ondersteuning voor dynamisch opnieuw laden van de geconfigureerde tolerantiestrategieën. Dit betekent dat u de configuratie van de tolerantiestrategieën tijdens runtime kunt wijzigen. Als u dynamisch opnieuw laden wilt inschakelen, gebruikt u de juiste AddResilienceHandler
overbelasting waarmee de ResilienceHandlerContext
. Gezien de context roept EnableReloads
u de bijbehorende opties voor de tolerantiestrategie aan:
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);
});
Met de voorgaande code wordt:
- Voegt een tolerantiehandler toe met de naam als de
pipelineName
servicecontainer"AdvancedPipeline"
. - Hiermee kunt u de pijplijn opnieuw laden
"AdvancedPipeline"
wanneer de benoemdeRetryStrategyOptions
opties veranderen. - Haalt de benoemde opties op uit de IOptionsMonitor<TOptions> service.
- Hiermee voegt u een strategie voor opnieuw proberen toe met de opgehaalde opties aan de opbouwfunctie voor tolerantie.
Zie Polly-documenten voor meer informatie: Geavanceerde afhankelijkheidsinjectie.
Dit voorbeeld is afhankelijk van een sectie met opties die kan worden gewijzigd, zoals een appsettings.json-bestand . Houd rekening met het volgende appsettings.json-bestand :
{
"RetryOptions": {
"Retry": {
"BackoffType": "Linear",
"UseJitter": false,
"MaxRetryAttempts": 7
}
}
}
Stel nu dat deze opties zijn gebonden aan de configuratie van de app, waarbij de HttpRetryStrategyOptions
"RetryOptions"
sectie wordt gekoppeld:
var section = builder.Configuration.GetSection("RetryOptions");
builder.Services.Configure<HttpStandardResilienceOptions>(section);
Zie het patroon Opties in .NET voor meer informatie.
Voorbeeld van gebruik
Uw app is afhankelijk van afhankelijkheidsinjectie om het ExampleClient
en bijbehorende probleem HttpClientop te lossen. De code bouwt de IServiceProvider code en lost de ExampleClient
ermee op.
IHost host = builder.Build();
ExampleClient client = host.Services.GetRequiredService<ExampleClient>();
await foreach (Comment? comment in client.GetCommentsAsync())
{
Console.WriteLine(comment);
}
Met de voorgaande code wordt:
- Bouwt de IServiceProvider van de ServiceCollection.
- Lost de
ExampleClient
fout op uit de IServiceProvider. - Roept de
GetCommentsAsync
methode aan omExampleClient
de opmerkingen op te halen. - Schrijft elke opmerking naar de console.
Stel dat het netwerk uitvalt of dat de server niet meer reageert. In het volgende diagram ziet u hoe de tolerantiestrategieën de situatie afhandelen, gezien de ExampleClient
en de GetCommentsAsync
methode:
In het voorgaande diagram ziet u:
- Er
ExampleClient
wordt een HTTP GET-aanvraag naar het/comments
eindpunt verzonden. - De HttpResponseMessage waarde wordt geëvalueerd:
- Als het antwoord is geslaagd (HTTP 200), wordt het antwoord geretourneerd.
- Als het antwoord mislukt (HTTP-niet-200), gebruikt de tolerantiepijplijn de geconfigureerde strategieën voor tolerantie.
Hoewel dit een eenvoudig voorbeeld is, ziet u hoe de tolerantiestrategieën kunnen worden gebruikt voor het afhandelen van tijdelijke fouten. Zie Polly-documenten voor meer informatie: Strategieën.
Bekende problemen
In de volgende secties worden verschillende bekende problemen beschreven.
Compatibiliteit met het Grpc.Net.ClientFactory
pakket
Als u versie 2.63.0
of eerder gebruiktGrpc.Net.ClientFactory
, kan het inschakelen van de standaardtolerantie- of hedging-handlers voor een gRPC-client een runtime-uitzondering veroorzaken. Bekijk met name het volgende codevoorbeeld:
services
.AddGrpcClient<Greeter.GreeterClient>()
.AddStandardResilienceHandler();
De voorgaande code resulteert in de volgende uitzondering:
System.InvalidOperationException: The ConfigureHttpClient method is not supported when creating gRPC clients. Unable to create client with name 'GreeterClient'.
U kunt dit probleem oplossen door een upgrade naar versie 2.64.0
of hoger uit te Grpc.Net.ClientFactory
voeren.
Er is een buildtijdcontrole die controleert of u versie 2.63.0
of eerder gebruikt Grpc.Net.ClientFactory
en als u de controle bent, een compilatiewaarschuwing genereert. U kunt de waarschuwing onderdrukken door de volgende eigenschap in uw projectbestand in te stellen:
<PropertyGroup>
<SuppressCheckGrpcNetClientFactoryVersion>true</SuppressCheckGrpcNetClientFactoryVersion>
</PropertyGroup>
Compatibiliteit met .NET Application Insights
Als u .NET Application Insights gebruikt, kan het inschakelen van tolerantiefunctionaliteit in uw toepassing ertoe leiden dat alle Application Insights-telemetrie ontbreekt. Het probleem treedt op wanneer de tolerantiefunctionaliteit wordt geregistreerd vóór Application Insights-services. Bekijk het volgende voorbeeld dat het probleem veroorzaakt:
// 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();
Het probleem wordt veroorzaakt door de volgende fout in Application Insights en kan worden opgelost door Application Insights-services te registreren vóór tolerantiefunctionaliteit, zoals hieronder wordt weergegeven:
// We register Application Insights first, and now it will be working correctly.
services.AddApplicationInsightsTelemetry();
services.AddHttpClient().AddStandardResilienceHandler();