حدث
١٧ رمضان، ٩ م - ٢١ رمضان، ١٠ ص
انضم إلى سلسلة الاجتماعات لإنشاء حلول الذكاء الاصطناعي قابلة للتطوير استنادا إلى حالات الاستخدام في العالم الحقيقي مع المطورين والخبراء الآخرين.
تسجيل الآنلم يعد هذا المتصفح مدعومًا.
بادر بالترقية إلى Microsoft Edge للاستفادة من أحدث الميزات والتحديثات الأمنية والدعم الفني.
Building robust HTTP apps that can recover from transient fault errors is a common requirement. This article assumes that you've already read Introduction to resilient app development, as this article extends the core concepts conveyed. To help build resilient HTTP apps, the Microsoft.Extensions.Http.Resilience NuGet package provides resilience mechanisms specifically for the HttpClient. This NuGet package relies on the Microsoft.Extensions.Resilience
library and Polly, which is a popular open-source project. For more information, see Polly.
To use resilience-patterns in HTTP apps, install the Microsoft.Extensions.Http.Resilience NuGet package.
dotnet add package Microsoft.Extensions.Http.Resilience --version 8.0.0
For more information, see dotnet add package or Manage package dependencies in .NET applications.
To add resilience to an HttpClient, you chain a call on the IHttpClientBuilder type that is returned from calling any of the available AddHttpClient methods. For more information, see IHttpClientFactory with .NET.
There are several resilience-centric extensions available. Some are standard, thus employing various industry best practices, and others are more customizable. When adding resilience, you should only add one resilience handler and avoid stacking handlers. If you need to add multiple resilience handlers, you should consider using the AddResilienceHandler
extension method, which allows you to customize the resilience strategies.
هام
All of the examples within this article rely on the AddHttpClient API, from the Microsoft.Extensions.Http library, which returns an IHttpClientBuilder instance. The IHttpClientBuilder instance is used to configure the HttpClient and add the resilience handler.
The standard resilience handler uses multiple resilience strategies stacked atop one another, with default options to send the requests and handle any transient errors. The standard resilience handler is added by calling the AddStandardResilienceHandler
extension method on an IHttpClientBuilder instance.
var services = new ServiceCollection();
var httpClientBuilder = services.AddHttpClient<ExampleClient>(
configureClient: static client =>
{
client.BaseAddress = new("https://jsonplaceholder.typicode.com");
});
The preceding code:
ExampleClient
type to the service container."https://jsonplaceholder.typicode.com"
as the base address.httpClientBuilder
that's used throughout the other examples within this article.A more real-world example would rely on hosting, such as that described in the .NET Generic Host article. Using the Microsoft.Extensions.Hosting NuGet package, consider the following updated example:
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");
});
The preceding code is similar to the manual ServiceCollection
creation approach, but instead relies on the Host.CreateApplicationBuilder() to build out a host that exposes the services.
The ExampleClient
is defined as follows:
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");
}
}
The preceding code:
ExampleClient
type that has a constructor that accepts an HttpClient.GetCommentsAsync
method that sends a GET request to the /comments
endpoint and returns the response.The Comment
type is defined as follows:
namespace Http.Resilience.Example;
public record class Comment(
int PostId, int Id, string Name, string Email, string Body);
Given that you've created an IHttpClientBuilder (httpClientBuilder
), and you now understand the ExampleClient
implementation and corresponding Comment
model, consider the following example:
httpClientBuilder.AddStandardResilienceHandler();
The preceding code adds the standard resilience handler to the HttpClient. Like most resilience APIs, there are overloads that allow you to customize the default options and applied resilience strategies.
The default configuration chains five resilience strategies in the following order (from the outermost to the innermost):
Order | Strategy | Description | Defaults |
---|---|---|---|
1 | Rate limiter | The rate limiter pipeline limits the maximum number of concurrent requests being sent to the dependency. | Queue: 0 Permit: 1_000 |
2 | Total timeout | The total request timeout pipeline applies an overall timeout to the execution, ensuring that the request, including retry attempts, doesn't exceed the configured limit. | Total timeout: 30s |
3 | Retry | The retry pipeline retries the request in case the dependency is slow or returns a transient error. | Max retries: 3 Backoff: Exponential Use jitter: true Delay:2s |
4 | Circuit breaker | The circuit breaker blocks the execution if too many direct failures or timeouts are detected. | Failure ratio: 10% Min throughput: 100 Sampling duration: 30s Break duration: 5s |
5 | Attempt timeout | The attempt timeout pipeline limits each request attempt duration and throws if it's exceeded. | Attempt timeout: 10s |
The retry and circuit breaker strategies both handle a set of specific HTTP status codes and exceptions. Consider the following HTTP status codes:
Additionally, these strategies handle the following exceptions:
HttpRequestException
TimeoutRejectedException
By default, the standard resilience handler is configured to make retries for all HTTP methods. For some applications, such behavior could be undesirable or even harmful. For example, if a POST request inserts a new record to a database, then making retries for such a request could lead to data duplication. If you need to disable retries for a given list of HTTP methods you can use the DisableFor(HttpRetryStrategyOptions, HttpMethod[]) method:
httpClientBuilder.AddStandardResilienceHandler(options =>
{
options.Retry.DisableFor(HttpMethod.Post, HttpMethod.Delete);
});
Alternatively, you can use the DisableForUnsafeHttpMethods(HttpRetryStrategyOptions) method, which disables retries for POST
, PATCH
, PUT
, DELETE
, and CONNECT
requests. According to RFC, these methods are considered unsafe; meaning their semantics are not read-only:
httpClientBuilder.AddStandardResilienceHandler(options =>
{
options.Retry.DisableForUnsafeHttpMethods();
});
The standard hedging handler wraps the execution of the request with a standard hedging mechanism. Hedging retries slow requests in parallel.
To use the standard hedging handler, call AddStandardHedgingHandler
extension method. The following example configures the ExampleClient
to use the standard hedging handler.
httpClientBuilder.AddStandardHedgingHandler();
The preceding code adds the standard hedging handler to the HttpClient.
The standard hedging uses a pool of circuit breakers to ensure that unhealthy endpoints aren't hedged against. By default, the selection from the pool is based on the URL authority (scheme + host + port).
تلميح
It's recommended that you configure the way the strategies are selected by calling StandardHedgingHandlerBuilderExtensions.SelectPipelineByAuthority
or StandardHedgingHandlerBuilderExtensions.SelectPipelineBy
for more advanced scenarios.
The preceding code adds the standard hedging handler to the IHttpClientBuilder. The default configuration chains five resilience strategies in the following order (from the outermost to the innermost):
Order | Strategy | Description | Defaults |
---|---|---|---|
1 | Total request timeout | The total request timeout pipeline applies an overall timeout to the execution, ensuring that the request, including hedging attempts, doesn't exceed the configured limit. | Total timeout: 30s |
2 | Hedging | The hedging strategy executes the requests against multiple endpoints in case the dependency is slow or returns a transient error. Routing is options, by default it just hedges the URL provided by the original HttpRequestMessage. | Min attempts: 1 Max attempts: 10 Delay: 2s |
3 | Rate limiter (per endpoint) | The rate limiter pipeline limits the maximum number of concurrent requests being sent to the dependency. | Queue: 0 Permit: 1_000 |
4 | Circuit breaker (per endpoint) | The circuit breaker blocks the execution if too many direct failures or timeouts are detected. | Failure ratio: 10% Min throughput: 100 Sampling duration: 30s Break duration: 5s |
5 | Attempt timeout (per endpoint) | The attempt timeout pipeline limits each request attempt duration and throws if it's exceeded. | Timeout: 10s |
When using the standard hedging handler, you can customize the way the request endpoints are selected by calling various extensions on the IRoutingStrategyBuilder
type. This can be useful for scenarios such as A/B testing, where you want to route a percentage of the requests to a different endpoint:
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 }
}
});
});
});
The preceding code:
IRoutingStrategyBuilder
to use the ConfigureOrderedGroups
method to configure the ordered groups.EndpointGroup
to the orderedGroup
that routes 3% of the requests to the https://example.net/api/experimental
endpoint and 97% of the requests to the https://example.net/api/stable
endpoint.IRoutingStrategyBuilder
to use the ConfigureWeightedGroups
method to configure theTo configure a weighted group, call the ConfigureWeightedGroups
method on the IRoutingStrategyBuilder
type. The following example configures the IRoutingStrategyBuilder
to use the ConfigureWeightedGroups
method to configure the weighted groups.
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 }
}
});
});
});
The preceding code:
IRoutingStrategyBuilder
to use the ConfigureWeightedGroups
method to configure the weighted groups.SelectionMode
to WeightedGroupSelectionMode.EveryAttempt
.WeightedEndpointGroup
to the weightedGroup
that routes 33% of the requests to the https://example.net/api/a
endpoint, 33% of the requests to the https://example.net/api/b
endpoint, and 33% of the requests to the https://example.net/api/c
endpoint.تلميح
The maximum number of hedging attempts directly correlates to the number of configured groups. For example, if you have two groups, the maximum number of attempts is two.
For more information, see Polly docs: Hedging resilience strategy.
It's common to configure either an ordered group or weighted group, but it's valid to configure both. Using ordered and weighted groups is helpful in scenarios where you want to send a percentage of the requests to a different endpoint, such is the case with A/B testing.
To have more control, you can customize the resilience handlers by using the AddResilienceHandler
API. This method accepts a delegate that configures the ResiliencePipelineBuilder<HttpResponseMessage>
instance that is used to create the resilience strategies.
To configure a named resilience handler, call the AddResilienceHandler
extension method with the name of the handler. The following example configures a named resilience handler called "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));
});
The preceding code:
"CustomPipeline"
as the pipelineName
to the service container.RequestTimeout
and TooManyRequests
HTTP status codes to the resilience builder.There are many options available for each of the resilience strategies. For more information, see the Polly docs: Strategies. For more information about configuring ShouldHandle
delegates, see Polly docs: Fault handling in reactive strategies.
Polly supports dynamic reloading of the configured resilience strategies. This means that you can change the configuration of the resilience strategies at run time. To enable dynamic reload, use the appropriate AddResilienceHandler
overload that exposes the ResilienceHandlerContext
. Given the context, call EnableReloads
of the corresponding resilience strategy options:
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);
});
The preceding code:
"AdvancedPipeline"
as the pipelineName
to the service container."AdvancedPipeline"
pipeline whenever the named RetryStrategyOptions
options change.For more information, see Polly docs: Advanced dependency injection.
This example relies on an options section that is capable of change, such as an appsettings.json file. Consider the following appsettings.json file:
{
"RetryOptions": {
"Retry": {
"BackoffType": "Linear",
"UseJitter": false,
"MaxRetryAttempts": 7
}
}
}
Now imagine that these options were bound to the app's configuration, binding the HttpRetryStrategyOptions
to the "RetryOptions"
section:
var section = builder.Configuration.GetSection("RetryOptions");
builder.Services.Configure<HttpStandardResilienceOptions>(section);
For more information, see Options pattern in .NET.
Your app relies on dependency injection to resolve the ExampleClient
and its corresponding HttpClient. The code builds the IServiceProvider and resolves the ExampleClient
from it.
IHost host = builder.Build();
ExampleClient client = host.Services.GetRequiredService<ExampleClient>();
await foreach (Comment? comment in client.GetCommentsAsync())
{
Console.WriteLine(comment);
}
The preceding code:
ExampleClient
from the IServiceProvider.GetCommentsAsync
method on the ExampleClient
to get the comments.Imagine a situation where the network goes down or the server becomes unresponsive. The following diagram shows how the resilience strategies would handle the situation, given the ExampleClient
and the GetCommentsAsync
method:
The preceding diagram depicts:
ExampleClient
sends an HTTP GET request to the /comments
endpoint.While this is a simple example, it demonstrates how the resilience strategies can be used to handle transient errors. For more information, see Polly docs: Strategies.
The following sections detail various known issues.
If you're using Grpc.Net.ClientFactory
version 2.63.0
or earlier, then enabling the standard resilience or hedging handlers for a gRPC client could cause a runtime exception. Specifically, consider the following code sample:
services
.AddGrpcClient<Greeter.GreeterClient>()
.AddStandardResilienceHandler();
The preceding code results in the following exception:
System.InvalidOperationException: The ConfigureHttpClient method is not supported when creating gRPC clients. Unable to create client with name 'GreeterClient'.
To resolve this issue, we recommend upgrading to Grpc.Net.ClientFactory
version 2.64.0
or later.
There's a build time check that verifies if you're using Grpc.Net.ClientFactory
version 2.63.0
or earlier, and if you are the check produces a compilation warning. You can suppress the warning by setting the following property in your project file:
<PropertyGroup>
<SuppressCheckGrpcNetClientFactoryVersion>true</SuppressCheckGrpcNetClientFactoryVersion>
</PropertyGroup>
If you're using .NET Application Insights, then enabling resilience functionality in your application could cause all Application Insights telemetry to be missing. The issue occurs when resilience functionality is registered before Application Insights services. Consider the following sample causing the issue:
// 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();
The issue is caused by the following bug in Application Insights and can be fixed by registering Application Insights services before resilience functionality, as shown below:
// We register Application Insights first, and now it will be working correctly.
services.AddApplicationInsightsTelemetry();
services.AddHttpClient().AddStandardResilienceHandler();
ملاحظات .NET
.NET هو مشروع مصدر مفتوح. حدد رابطًا لتقديم الملاحظات:
حدث
١٧ رمضان، ٩ م - ٢١ رمضان، ١٠ ص
انضم إلى سلسلة الاجتماعات لإنشاء حلول الذكاء الاصطناعي قابلة للتطوير استنادا إلى حالات الاستخدام في العالم الحقيقي مع المطورين والخبراء الآخرين.
تسجيل الآنالتدريب
الوحدة النمطية
تنفيذ المرونة في خدمة مصغرة أصلية على السحابة - Training
ترشدك هذه الوحدة خلال تنفيذ المرونة في تطبيق الخدمات المصغرة .NET في خدمة Kubernetes.