Dela via


Skapa motståndskraftiga HTTP-appar: Viktiga utvecklingsmönster

Att skapa robusta HTTP-appar som kan återställas från tillfälliga fel är ett vanligt krav. Den här artikeln förutsätter att du redan har läst Introduktion till elastisk apputveckling, eftersom den här artikeln utökar de grundläggande begrepp som förmedlas. För att hjälpa till att skapa motståndskraftiga HTTP-appar tillhandahåller NuGet-paketet Microsoft.Extensions.Http.Resilience motståndskraftsmekanismer specifikt för HttpClient. Det här NuGet-paketet förlitar sig på Microsoft.Extensions.Resilience biblioteket och Polly, som är ett populärt projekt med öppen källkod. Mer information finns i Polly.

Kom igång

Om du vill använda motståndskraftsmönster i HTTP-appar installerar du NuGet-paketet Microsoft.Extensions.Http.Resilience .

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

Mer information finns i dotnet add package or Manage package dependencies in .NET applications (Dotnet add package or Manage package dependencies in .NET applications).

Lägga till motståndskraft till en HTTP-klient

Om du vill lägga till motståndskraft i en HttpClientkedjar du ett anrop på den IHttpClientBuilder typ som returneras från att anropa någon av de tillgängliga AddHttpClient metoderna. Mer information finns i IHttpClientFactory med .NET.

Det finns flera motståndskraftscentrerade tillägg tillgängliga. Vissa är standard och använder därför olika branschtips, och andra är mer anpassningsbara. När du lägger till motståndskraft bör du bara lägga till en motståndskraftshanterare och undvika staplingshanterare. Om du behöver lägga till flera motståndskraftshanterare bör du överväga att använda AddResilienceHandler tilläggsmetoden, som gör att du kan anpassa återhämtningsstrategierna.

Viktigt!

Alla exempel i den här artikeln förlitar sig på API:et AddHttpClient , från biblioteket Microsoft.Extensions.Http , som returnerar en IHttpClientBuilder instans. Instansen IHttpClientBuilder används för att konfigurera HttpClient och lägga till motståndskraftshanteraren.

Lägg till standardhanteringshanterare för motståndskraft

Standardhanteraren för motståndskraft använder flera återhämtningsstrategier staplade ovanpå varandra, med standardalternativ för att skicka begäranden och hantera tillfälliga fel. Standardhanteraren för motståndskraft läggs till genom att anropa AddStandardResilienceHandler tilläggsmetoden på en IHttpClientBuilder instans.

var services = new ServiceCollection();

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

Koden ovan:

  • Skapar en ServiceCollection instans.
  • Lägger till en HttpClient för ExampleClient typen i tjänstcontainern.
  • Konfigurerar HttpClient att använda "https://jsonplaceholder.typicode.com" som basadress.
  • Skapar det httpClientBuilder som används i de andra exemplen i den här artikeln.

Ett mer verkligt exempel skulle förlita sig på värdtjänster, till exempel det som beskrivs i artikeln .NET Generic Host . Använd NuGet-paketet Microsoft.Extensions.Hosting och tänk på följande uppdaterade exempel:

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");
    });

Föregående kod liknar metoden för manuell ServiceCollection skapande, men förlitar sig i stället på Host.CreateApplicationBuilder() att bygga ut en värd som exponerar tjänsterna.

ExampleClient Definieras på följande sätt:

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");
    }
}

Koden ovan:

  • Definierar en ExampleClient typ som har en konstruktor som accepterar en HttpClient.
  • Exponerar en GetCommentsAsync metod som skickar en GET-begäran till /comments slutpunkten och returnerar svaret.

Typen Comment definieras på följande sätt:

namespace Http.Resilience.Example;

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

Med tanke på att du har skapat en IHttpClientBuilder (httpClientBuilder) och nu förstår implementeringen ExampleClient och motsvarande Comment modell bör du överväga följande exempel:

httpClientBuilder.AddStandardResilienceHandler();

Föregående kod lägger till standardhanteraren för motståndskraft i HttpClient. Precis som de flesta återhämtnings-API:er finns det överlagringar som gör att du kan anpassa standardalternativen och tillämpade återhämtningsstrategier.

Standardinställningar för motståndskraftshanterare

Standardkonfigurationen kedjar fem återhämtningsstrategier i följande ordning (från den yttersta till den innersta):

Order Strategi beskrivning Defaults
1 Hastighetsbegränsning Pipelinen för hastighetsbegränsning begränsar det maximala antalet samtidiga begäranden som skickas till beroendet. Kö: 0
Tillåta: 1_000
2 Total timeout Den totala tidsgränsen för begäran tillämpar en övergripande tidsgräns för körningen, vilket säkerställer att begäran, inklusive återförsök, inte överskrider den konfigurerade gränsen. Total timeout: 30s
3 Försök igen Pipelinen för återförsök försöker skicka begäran om beroendet är långsamt eller returnerar ett tillfälligt fel. Maximalt antal återförsök: 3
Backoff: Exponential
Använd jitter: true
Fördröjning:2s
4 Kretsbrytaren Kretsbrytaren blockerar körningen om för många direkta fel eller tidsgränser identifieras. Felförhållande: 10 %
Minsta dataflöde: 100
Samplingstid: 30-talet
Pausvaraktighet: 5s
5 Tidsgräns för försök Tidsgränspipelinen för försök begränsar varje varaktighet för begärandeförsök och utlöser om den har överskridits. Tidsgräns för försök: 10-talet

Återförsök och kretsbrytare

Strategierna för återförsök och kretsbrytare hanterar båda en uppsättning specifika HTTP-statuskoder och undantag. Överväg följande HTTP-statuskoder:

  • HTTP 500 och senare (serverfel)
  • HTTP 408 (tidsgräns för begäran)
  • HTTP 429 (för många begäranden)

Dessutom hanterar dessa strategier följande undantag:

  • HttpRequestException
  • TimeoutRejectedException

Inaktivera återförsök för en viss lista över HTTP-metoder

Standardhanteraren för motståndskraft är som standard konfigurerad för att göra återförsök för alla HTTP-metoder. För vissa program kan sådant beteende vara oönskat eller till och med skadligt. Om en POST-begäran till exempel infogar en ny post i en databas kan det leda till dataduplicering om du gör återförsök för en sådan begäran. Om du behöver inaktivera återförsök för en viss lista över HTTP-metoder kan du använda metoden DisableFor(HttpRetryStrategyOptions, HttpMethod[]):

httpClientBuilder.AddStandardResilienceHandler(options =>
{
    options.Retry.DisableFor(HttpMethod.Post, HttpMethod.Delete);
});

Du kan också använda metoden DisableForUnsafeHttpMethods(HttpRetryStrategyOptions), som inaktiverar återförsök för POST, PATCH, PUT, DELETEoch CONNECT begäranden. Enligt RFCanses dessa metoder vara osäkra; vilket innebär att deras semantik inte är enbart läsbar.

httpClientBuilder.AddStandardResilienceHandler(options =>
{
    options.Retry.DisableForUnsafeHttpMethods();
});

Lägg till standardsäkringshanterare

Standardsäkringshanteraren omsluter körningen av begäran med en standardsäkringsmekanism. Säkring av återförsök långsamma begäranden parallellt.

Om du vill använda standardsäkringshanteraren anropar AddStandardHedgingHandler du tilläggsmetoden. I följande exempel konfigureras ExampleClient att använda standardsäkringshanteraren.

httpClientBuilder.AddStandardHedgingHandler();

Föregående kod lägger till standardsäkringshanteraren i HttpClient.

Standardinställningar för säkringshanterare

Standardsäkringen använder en pool med kretsbrytare för att säkerställa att ej felfria slutpunkter inte skyddas mot. Som standard baseras valet från poolen på URL-utfärdaren (schema + värd + port).

Dricks

Vi rekommenderar att du konfigurerar hur strategierna väljs genom att anropa StandardHedgingHandlerBuilderExtensions.SelectPipelineByAuthority eller StandardHedgingHandlerBuilderExtensions.SelectPipelineBy för mer avancerade scenarier.

Föregående kod lägger till standardsäkringshanteraren i IHttpClientBuilder. Standardkonfigurationen kedjar fem återhämtningsstrategier i följande ordning (från den yttersta till den innersta):

Order Strategi beskrivning Defaults
1 Total tidsgräns för begäran Den totala tidsgränsen för begäran tillämpar en övergripande tidsgräns för körningen, vilket säkerställer att begäran, inklusive säkringsförsök, inte överskrider den konfigurerade gränsen. Total timeout: 30s
2 Säkring Säkringsstrategin kör begäranden mot flera slutpunkter om beroendet är långsamt eller returnerar ett tillfälligt fel. Routning är alternativ, som standard säkrar den bara URL:en som tillhandahålls av den ursprungliga HttpRequestMessage. Minsta antal försök: 1
Maximalt antal försök: 10
Fördröjning: 2s
3 Hastighetsbegränsning (per slutpunkt) Pipelinen för hastighetsbegränsning begränsar det maximala antalet samtidiga begäranden som skickas till beroendet. Kö: 0
Tillåta: 1_000
4 Kretsbrytare (per slutpunkt) Kretsbrytaren blockerar körningen om för många direkta fel eller tidsgränser identifieras. Felförhållande: 10 %
Minsta dataflöde: 100
Samplingstid: 30-talet
Pausvaraktighet: 5s
5 Tidsgräns för försök (per slutpunkt) Tidsgränspipelinen för försök begränsar varje varaktighet för begärandeförsök och utlöser om den har överskridits. Tidsgräns: 10-talet

Anpassa val av säkringshanterares routning

När du använder standardsäkringshanteraren kan du anpassa hur begärandeslutpunkterna väljs genom att anropa olika tillägg för IRoutingStrategyBuilder typen. Detta kan vara användbart för scenarier som A/B-testning, där du vill dirigera en procentandel av begäranden till en annan slutpunkt:

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

Koden ovan:

  • Lägger till säkringshanteraren i IHttpClientBuilder.
  • Konfigurerar IRoutingStrategyBuilder att använda ConfigureOrderedGroups metoden för att konfigurera de ordnade grupperna.
  • Lägger till en EndpointGroup till orderedGroup som dirigerar 3 % av begäranden till https://example.net/api/experimental slutpunkten och 97 % av begäranden till https://example.net/api/stable slutpunkten.
  • Konfigurerar IRoutingStrategyBuilder att använda ConfigureWeightedGroups metoden för att konfigurera

Om du vill konfigurera en viktad grupp anropar ConfigureWeightedGroups du metoden för IRoutingStrategyBuilder typen . I följande exempel konfigureras IRoutingStrategyBuilder att använda ConfigureWeightedGroups metoden för att konfigurera de viktade grupperna.

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

Koden ovan:

  • Lägger till säkringshanteraren i IHttpClientBuilder.
  • Konfigurerar IRoutingStrategyBuilder att använda ConfigureWeightedGroups metoden för att konfigurera de viktade grupperna.
  • SelectionMode Anger till WeightedGroupSelectionMode.EveryAttempt.
  • Lägger till en WeightedEndpointGroup till weightedGroup som dirigerar 33 % av begäranden till https://example.net/api/a slutpunkten, 33 % av begäranden till https://example.net/api/b slutpunkten och 33 % av begäranden till https://example.net/api/c slutpunkten.

Dricks

Det maximala antalet säkringsförsök korrelerar direkt med antalet konfigurerade grupper. Om du till exempel har två grupper är det maximala antalet försök två.

Mer information finns i Polly-dokument: Strategi för säkring av motståndskraft.

Det är vanligt att konfigurera antingen en ordnad grupp eller en viktad grupp, men det är giltigt att konfigurera båda. Att använda ordnade och viktade grupper är användbart i scenarier där du vill skicka en procentandel av begäranden till en annan slutpunkt, så är fallet med A/B-testning.

Lägga till anpassade motståndskraftshanterare

Om du vill ha mer kontroll kan du anpassa motståndskraftshanterare med hjälp av API:et AddResilienceHandler . Den här metoden accepterar ett ombud som konfigurerar den ResiliencePipelineBuilder<HttpResponseMessage> instans som används för att skapa återhämtningsstrategierna.

Om du vill konfigurera en namngiven AddResilienceHandler motståndskraftshanterare anropar du tilläggsmetoden med namnet på hanteraren. I följande exempel konfigureras en namngiven motståndskraftshanterare med namnet "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));
});

Koden ovan:

  • Lägger till en motståndskraftshanterare med namnet "CustomPipeline" som pipelineName till tjänstcontainern.
  • Lägger till en strategi för återförsök med exponentiell backoff, fem återförsök och jitter-inställningar för motståndskraftsverktyget.
  • Lägger till en strategi för kretsbrytare med en samplingstid på 10 sekunder, ett felförhållande på 0,2 (20 %), ett minsta dataflöde på tre och ett predikat som hanterar och RequestTimeout HTTP-statuskoder till motståndskraftsverktygetTooManyRequests.
  • Lägger till en timeout-strategi med en timeout på fem sekunder till motståndskraftsverktyget.

Det finns många tillgängliga alternativ för var och en av återhämtningsstrategierna. Mer information finns i Polly-dokumenten: Strategier. Mer information om hur du konfigurerar ShouldHandle ombud finns i Polly-dokument: Felhantering i reaktiva strategier.

Dynamisk omläsning

Polly stöder dynamisk omladdning av de konfigurerade återhämtningsstrategierna. Det innebär att du kan ändra konfigurationen av återhämtningsstrategierna vid körning. Om du vill aktivera dynamisk omläsning använder du lämplig AddResilienceHandler överlagring som exponerar ResilienceHandlerContext. Med tanke på kontexten, anrop EnableReloads av motsvarande återhämtningsstrategialternativ:

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

Koden ovan:

  • Lägger till en motståndskraftshanterare med namnet "AdvancedPipeline" som pipelineName till tjänstcontainern.
  • Aktiverar återinläsningarna av pipelinen "AdvancedPipeline" när de namngivna RetryStrategyOptions alternativen ändras.
  • Hämtar de namngivna alternativen från IOptionsMonitor<TOptions> tjänsten.
  • Lägger till en återförsöksstrategi med de hämtade alternativen till motståndskraftsverktyget.

Mer information finns i Polly-dokument: Avancerad beroendeinmatning.

Det här exemplet förlitar sig på ett alternativavsnitt som kan ändras, till exempel en appsettings.json fil. Överväg följande appsettings.json fil:

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

Anta nu att de här alternativen var bundna till appens konfiguration och binder HttpRetryStrategyOptions till avsnittet "RetryOptions" :

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

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

Mer information finns i Alternativmönster i .NET.

Exempel på användning

Din app förlitar sig på beroendeinmatning för att lösa ExampleClient och dess motsvarande HttpClient. Koden skapar IServiceProvider och löser ExampleClient från den.

IHost host = builder.Build();

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

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

Koden ovan:

Tänk dig en situation där nätverket slutar fungera eller om servern inte svarar. Följande diagram visar hur återhämtningsstrategierna skulle hantera situationen, med tanke på ExampleClient metoden och GetCommentsAsync :

Exempel på HTTP GET-arbetsflöde med resilienspipeline.

Föregående diagram visar:

  • ExampleClient Skickar en HTTP GET-begäran till /comments slutpunkten.
  • HttpResponseMessage Utvärderas:
    • Om svaret lyckas (HTTP 200) returneras svaret.
    • Om svaret misslyckas (HTTP icke-200) använder motståndskraftspipelinen de konfigurerade återhämtningsstrategierna.

Även om det här är ett enkelt exempel visar det hur återhämtningsstrategierna kan användas för att hantera tillfälliga fel. Mer information finns i Polly-dokument: Strategier.

Kända problem

I följande avsnitt beskrivs olika kända problem.

Kompatibilitet med Grpc.Net.ClientFactory paketet

Om du använder Grpc.Net.ClientFactory version 2.63.0 eller tidigare kan det orsaka ett körningsundantag att aktivera standardhanteraren för motståndskraft eller säkring för en gRPC-klient. Mer specifikt bör du överväga följande kodexempel:

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

Föregående kod resulterar i följande undantag:

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

För att lösa det här problemet rekommenderar vi att du uppgraderar till Grpc.Net.ClientFactory version 2.64.0 eller senare.

Det finns en byggtidskontroll som verifierar om du använder Grpc.Net.ClientFactory version 2.63.0 eller tidigare, och om du är kontrollen genererar en kompileringsvarning. Du kan ignorera varningen genom att ange följande egenskap i projektfilen:

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

Kompatibilitet med .NET Application Insights

Om du använder .NET Application Insights kan aktivering av motståndskraftsfunktioner i ditt program leda till att all Application Insights-telemetri saknas. Problemet uppstår när motståndskraftsfunktioner registreras före Application Insights-tjänster. Överväg följande exempel som orsakar problemet:

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

Problemet orsakas av följande bugg i Application Insights och kan åtgärdas genom registrering av Application Insights-tjänster före motståndskraftsfunktioner, som visas nedan:

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