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
, DELETE
och 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ändaConfigureOrderedGroups
metoden för att konfigurera de ordnade grupperna. - Lägger till en
EndpointGroup
tillorderedGroup
som dirigerar 3 % av begäranden tillhttps://example.net/api/experimental
slutpunkten och 97 % av begäranden tillhttps://example.net/api/stable
slutpunkten. - Konfigurerar
IRoutingStrategyBuilder
att användaConfigureWeightedGroups
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ändaConfigureWeightedGroups
metoden för att konfigurera de viktade grupperna. -
SelectionMode
Anger tillWeightedGroupSelectionMode.EveryAttempt
. - Lägger till en
WeightedEndpointGroup
tillweightedGroup
som dirigerar 33 % av begäranden tillhttps://example.net/api/a
slutpunkten, 33 % av begäranden tillhttps://example.net/api/b
slutpunkten och 33 % av begäranden tillhttps://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"
sompipelineName
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"
sompipelineName
till tjänstcontainern. - Aktiverar återinläsningarna av pipelinen
"AdvancedPipeline"
när de namngivnaRetryStrategyOptions
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:
- IServiceProvider Skapar från ServiceCollection.
- Löser
ExampleClient
från IServiceProvider. -
GetCommentsAsync
Anropar metoden påExampleClient
för att hämta kommentarerna. - Skriver varje kommentar till konsolen.
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
:
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();