Delen via


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 de orderedGroup aanvraag toegevoegd waarmee 3% van de aanvragen naar het https://example.net/api/experimental eindpunt wordt gerouteerd en 97% van de aanvragen naar het https://example.net/api/stable eindpunt.
  • Hiermee configureert u de IRoutingStrategyBuilder methode voor het ConfigureWeightedGroups 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 op WeightedGroupSelectionMode.EveryAttempt.
  • Hiermee wordt een WeightedEndpointGroup aan de weightedGroup aanvraag toegevoegd waarmee 33% van de aanvragen naar het https://example.net/api/a eindpunt wordt gerouteerd, 33% van de aanvragen naar het https://example.net/api/b eindpunt en 33% van de aanvragen naar het https://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 en TooManyRequests 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 benoemde RetryStrategyOptions 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:

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:

Voorbeeld van EEN HTTP GET-werkstroom met een tolerantiepijplijn.

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