Standard Resilience
Resilience is not an easy topic, and the official documentation could be difficult to understand.
On your Aspire project when you call
builder.AddServiceDefaults()
it really calls
public static TBuilder AddServiceDefaults<TBuilder>(this TBuilder builder
) where TBuilder : IHostApplicationBuilder
{
.....
builder.Services.ConfigureHttpClientDefaults(http =>
{
// Turn on resilience by default
http.AddStandardResilienceHandler();
// Turn on service discovery by default
http.AddServiceDiscovery();
});
It will add the default standard resilience, which is basically by default :
- 10 sec timeout for each HTTP request
- 3 retry in case of failure/timeout above
- 30 sec timeout total
- open the circuit for 5 sec if 10% of the http requests fail (sliding window of 30 sec after 100 requests have been called)
- set HttpClient.Timeout to Infinite (because it’s legacy)
.AddRateLimiter(options.RateLimiter) // 1000 + OldestFirst
.AddTimeout(options.TotalRequestTimeout) // 30sec for all processing
.AddRetry(options.Retry) // 3 retry
.AddCircuitBreaker(options.CircuitBreaker) //0.1 - 100 - 30sec sampl - 5sec duration break
.AddTimeout(options.AttemptTimeout) // 10 sec per try;
I really don’t like the fact everything is magic, and my goal is to move the whole settings in the appsettings.json like that :
public static TBuilder AddServiceDefaults<TBuilder>(this TBuilder builder,
IConfigurationSection resilienceConfigurationSection
) where TBuilder : IHostApplicationBuilder
{
.....
builder.Services.ConfigureHttpClientDefaults(http =>
{
// Turn on resilience by default reading the configuration
http.AddStandardResilienceHandler(resilienceConfigurationSection);
// Turn on service discovery by default
http.AddServiceDiscovery();
});
and then call it like
builder.AddServiceDefaults(builder.Configuration.GetSection("Resilience:Standard"));
Add these parameters in the appsettings.json
{
....
"Resilience": {
"Standard": {
"RateLimiter": {
"Name": "Standard-RateLimiter",
"DefaultRateLimiterOptions": {
"PermitLimit": 1000,
"QueueLimit": 0,
"QueueProcessingOrder": "OldestFirst"
}
},
"AttemptTimeout": {
"Name": "Standard-AttemptTimeout",
"Timeout": "00:00:10"
}
"TotalRequestTimeout": {
"Name": "Standard-TotalRequestTimeout",
"Timeout": "00:00:30"
},
"Retry": {
"Name": "Standard-Retry",
"ShouldRetryAfterHeader": true,
"MaxRetryAttempts": 3,
"BackoffType": "Exponential",
"UseJitter": true,
"Delay": "00:00:02",
"MaxDelay": null
},
"CircuitBreaker": {
"Name": "Standard-CircuitBreaker",
"FailureRatio": 0.1,
"MinimumThroughput": 100,
"SamplingDuration": "00:00:30",
"BreakDuration": "00:00:05",
"ManualControl": null,
"StateProvider": null
}
}
}
}
here we are, you have the default standard strategy available through the appSettings !
How to add a customer strategy for a named HttpClient ?
You need to remove the previous one with RemoveAllResilienceHandlers, set the legacy HttpTimeout to infinite (because Resilience will handle it) :
// 1. define the resilience builder
var section = builder.Configuration.GetSection("Resilience:Custom");
builder.Services.Configure<HttpStandardResilienceOptions>("CustomResilience", section);
var resiliencePipelineBuilder =
(ResiliencePipelineBuilder<HttpResponseMessage> builder, ResilienceHandlerContext context) =>
{
// Optional : enable reloads dynamically when changing the appsettings
context.EnableReloads<HttpStandardResilienceOptions>("CustomResilience");
// Retrieve the named options
var retryOptions =
context.GetOptions<HttpStandardResilienceOptions>("CustomResilience");
// Rebuild all properties, order is important
builder.AddRateLimiter(retryOptions.RateLimiter);
builder.AddTimeout(retryOptions.TotalRequestTimeout);
builder.AddRetry(retryOptions.Retry);
builder.AddCircuitBreaker(retryOptions.CircuitBreaker);
builder.AddTimeout(retryOptions.AttemptTimeout);
};
// ...
#pragma warning disable EXTEXP0001 //to make work RemoveAllResilienceHandlers
builder.Services.AddHttpClient<IMyService, MyService>((client) =>
{
client.BaseAddress = new Uri($"https+http://my-service-api/Servers/");
})
.RemoveAllResilienceHandlers()
.ConfigureHttpClient(client => client.Timeout = Timeout.InfiniteTimeSpan)
.AddResilienceHandler("custom", resiliencePipelineBuilder);
#pragma warning restore EXTEXP0001
and copy past the Resilience:Standard to a new one in the appsettings.json :
"Resilience": {
"Standard": {
}
....
"Custom": {
"RateLimiter": {
"Name": "Custom-RateLimiter",
...
}
}
}
How to make work only one retry
Imagine you only want one try of maximum 5sec, and don’t want to have retry, here are the settings to change :
"Custom": {
...
"AttemptTimeout": {
"Name": "Custom-AttemptTimeout",
"Timeout": "00:00:05"
},
"TotalRequestTimeout": {
"Name": "Custom-TotalRequestTimeout",
"Timeout": "00:00:05"
},
"Retry": {
"Name": "Custom-Retry",
"ShouldRetryAfterHeader": true,
"MaxRetryAttempts": 1,
"BackoffType": "Exponential",
"UseJitter": true,
"Delay": "00:00:02",
"MaxDelay": null
},
...
}