Eventos
Compilación de Intelligent Apps
17 mar, 23 - 21 mar, 23
Únase a la serie de reuniones para crear soluciones de inteligencia artificial escalables basadas en casos de uso reales con compañeros desarrolladores y expertos.
Regístrese ahoraEste explorador ya no se admite.
Actualice a Microsoft Edge para aprovechar las características y actualizaciones de seguridad más recientes, y disponer de soporte técnico.
La compilación de aplicaciones HTTP sólidas que pueden recuperarse de errores transitorios es un requisito común. En este artículo se da por supuesto que ya ha leído Introducción al desarrollo de aplicaciones resistentes, ya que en este artículo se amplían los conceptos básicos que se han transmitido. Para ayudar a compilar aplicaciones HTTP resistentes, el paquete NuGet Microsoft.Extensions.Http.Resilience proporciona mecanismos de resistencia específicamente para el HttpClient. Este paquete NuGet se basa en la biblioteca Microsoft.Extensions.Resilience
y Polly, que es un proyecto de código abierto popular. Para obtener más información, consulte Polly.
Para usar patrones de resistencia en aplicaciones HTTP, instale el paquete NuGet Microsoft.Extensions.Http.Resilience.
dotnet add package Microsoft.Extensions.Http.Resilience --version 8.0.0
Para obtener más información, consulte dotnet add package o Administración de dependencias de paquetes en aplicaciones .NET.
Para agregar resistencia a un HttpClient, se encadena una llamada al tipo IHttpClientBuilder que se devuelve al llamar a cualquiera de los métodos AddHttpClient disponibles. Para obtener más información, vea IHttpClientFactory con .NET.
Hay varias extensiones centradas en la resistencia disponibles. Algunas son estándar, por lo que emplean varios procedimientos recomendados del sector, y otras son más personalizables. Al agregar resistencia, solo debe agregar un controlador de resistencia y evitar controladores de apilamiento. Si necesita agregar varios controladores de resistencia, debe considerar el uso del método de extensión AddResilienceHandler
, lo que le permite personalizar las estrategias de resistencia.
Importante
Todos los ejemplos de este artículo se basan en la API AddHttpClient, de la biblioteca Microsoft.Extensions.Http, que devuelve una instancia IHttpClientBuilder. La instancia IHttpClientBuilder se usa para configurar el HttpClient y agregar el controlador de resistencia.
El controlador de resistencia estándar usa varias estrategias de resistencia apiladas entre sí, con opciones predeterminadas para enviar las solicitudes y controlar los errores transitorios. El controlador de resistencia estándar se agrega llamando al método de extensión AddStandardResilienceHandler
en una instancia IHttpClientBuilder.
var services = new ServiceCollection();
var httpClientBuilder = services.AddHttpClient<ExampleClient>(
configureClient: static client =>
{
client.BaseAddress = new("https://jsonplaceholder.typicode.com");
});
El código anterior:
ExampleClient
al contenedor de servicios."https://jsonplaceholder.typicode.com"
como dirección base.httpClientBuilder
que se usa en los otros ejemplos de este artículo.Un ejemplo más real se basaría en el hospedaje, como el descrito en el artículo Host genérico de .NET. Con el paquete NuGet Microsoft.Extensions.Hosting, considere el siguiente ejemplo actualizado:
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");
});
El código anterior es similar al enfoque de creación manual ServiceCollection
, pero en su lugar se basa en el Host.CreateApplicationBuilder() para compilar un host que expone los servicios.
El ExampleClient
se define de la siguiente manera:
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");
}
}
El código anterior:
ExampleClient
que tiene un constructor que acepta un HttpClient.GetCommentsAsync
que envía una solicitud GET al punto de conexión /comments
y devuelve la respuesta.El tipo Comment
se define de la siguiente manera:
namespace Http.Resilience.Example;
public record class Comment(
int PostId, int Id, string Name, string Email, string Body);
Dado que ha creado un IHttpClientBuilder (httpClientBuilder
) y ahora comprende la implementación de ExampleClient
y el modelo correspondiente de Comment
, considere el siguiente ejemplo:
httpClientBuilder.AddStandardResilienceHandler();
El código anterior agrega el controlador de resistencia estándar a HttpClient. Al igual que la mayoría de las API de resistencia, hay sobrecargas que permiten personalizar las opciones predeterminadas y las estrategias de resistencia aplicadas.
La configuración predeterminada encadena cinco estrategias de resistencia en el siguiente orden (desde el exterior hasta el más interno):
Orden | Estrategia | Descripción | Defaults |
---|---|---|---|
1 | Limitador de velocidad | La canalización del limitador de velocidad limita el número máximo de solicitudes simultáneas que se envían a la dependencia. | Queue: 0 Permiso: 1_000 |
2 | Tiempo de expiración total | La canalización total de tiempo de espera de solicitud aplica un tiempo de espera general a la ejecución, lo que garantiza que la solicitud, incluidos los intentos de reintento, no supere el límite configurado. | Tiempo de expiración total: 30s |
3 | Volver a intentar | La canalización de reintento reintenta la solicitud en caso de que la dependencia sea lenta o devuelva un error transitorio. | Número máximo de reintentos: 3 Retroceso: Exponential Usar vibración: true Retraso:2s |
4 | Interruptor automático | El interruptor bloquea la ejecución si se detectan demasiados errores directos o tiempos de espera. | Proporción de errores: 10 % Rendimiento mínimo: 100 Duración del muestreo: 30s Duración de descanso: 5s |
5 | Tiempo de espera de intento | La canalización de tiempo de espera de intento limita la duración de cada intento de solicitud e inicia si se supera. | Tiempo de espera de intento: 10s |
Las estrategias de reintentos y disyuntor controlan un conjunto de códigos de estado HTTP y excepciones específicos. Tenga en cuenta los siguientes códigos de estado HTTP:
Además, estas estrategias controlan las siguientes excepciones:
HttpRequestException
TimeoutRejectedException
De forma predeterminada, el controlador de resistencia estándar está configurado para realizar reintentos para todos los métodos HTTP. En algunas aplicaciones, este comportamiento podría no ser deseable o incluso perjudicial. Por ejemplo, si una solicitud POST inserta un nuevo registro en una base de datos, realizar reintentos para dicha solicitud podría provocar la duplicación de datos. Si necesita deshabilitar reintentos para una lista determinada de métodos HTTP, puede usar el método DisableFor(HttpRetryStrategyOptions, HttpMethod[]):
httpClientBuilder.AddStandardResilienceHandler(options =>
{
options.Retry.DisableFor(HttpMethod.Post, HttpMethod.Delete);
});
Como alternativa, puede usar el método DisableForUnsafeHttpMethods(HttpRetryStrategyOptions), que deshabilita los reintentos para las solicitudes de POST
, PATCH
, PUT
, DELETE
y CONNECT
. Según RFC, estos métodos se consideran no seguros; significa que su semántica no es de solo lectura:
httpClientBuilder.AddStandardResilienceHandler(options =>
{
options.Retry.DisableForUnsafeHttpMethods();
});
El controlador de cobertura estándar ajusta la ejecución de la solicitud con un mecanismo de cobertura estándar. La cobertura reintenta las solicitudes lentas en paralelo.
Para usar el controlador de cobertura estándar, llame al método de extensión AddStandardHedgingHandler
. En el siguiente ejemplo se configura el ExampleClient
para usar el controlador de cobertura estándar.
httpClientBuilder.AddStandardHedgingHandler();
El código anterior agrega el controlador de cobertura estándar al HttpClient.
La cobertura estándar usa un grupo de disyuntores para asegurarse de que los puntos de conexión incorrectos no están cubiertos. De forma predeterminada, la selección del grupo se basa en la entidad de dirección URL (esquema + host + puerto).
Sugerencia
Se recomienda configurar la forma en que se seleccionan las estrategias mediante una llamada a StandardHedgingHandlerBuilderExtensions.SelectPipelineByAuthority
o StandardHedgingHandlerBuilderExtensions.SelectPipelineBy
para escenarios más avanzados.
El código anterior agrega el controlador de cobertura estándar al IHttpClientBuilder. La configuración predeterminada encadena cinco estrategias de resistencia en el siguiente orden (desde el exterior hasta el más interno):
Orden | Estrategia | Descripción | Defaults |
---|---|---|---|
1 | Tiempo de espera total de solicitud | La canalización total de tiempo de espera de solicitud aplica un tiempo de espera general a la ejecución, lo que garantiza que la solicitud, incluidos los intentos de cobertura, no supere el límite configurado. | Tiempo de expiración total: 30s |
2 | Cobertura | La estrategia de cobertura ejecuta las solicitudes en varios puntos de conexión en caso de que la dependencia sea lenta o devuelva un error transitorio. El enrutamiento es opcional, de forma predeterminada solo cubre la dirección URL proporcionada por el HttpRequestMessage original. | Intentos mínimos: 1 Intentos máximos: 10 Retraso: 2s |
3 | Limitador de velocidad (por punto de conexión) | La canalización del limitador de velocidad limita el número máximo de solicitudes simultáneas que se envían a la dependencia. | Queue: 0 Permiso: 1_000 |
4 | Interruptor (por punto de conexión) | El interruptor bloquea la ejecución si se detectan demasiados errores directos o tiempos de espera. | Proporción de errores: 10 % Rendimiento mínimo: 100 Duración del muestreo: 30s Duración de descanso: 5s |
5 | Tiempo de espera de intento (por punto de conexión) | La canalización de tiempo de espera de intento limita la duración de cada intento de solicitud e inicia si se supera. | Tiempo de expiración: 10s |
Al usar el controlador de cobertura estándar, puede personalizar la forma en que se seleccionan los puntos de conexión de solicitud llamando a varias extensiones en el tipo IRoutingStrategyBuilder
. Esto puede ser útil para escenarios como las pruebas A/B, en las que desea dirigir un porcentaje de las solicitudes a un punto de conexión diferente:
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 }
}
});
});
});
El código anterior:
IRoutingStrategyBuilder
para usar el método ConfigureOrderedGroups
para configurar los grupos ordenados.EndpointGroup
al orderedGroup
que enruta el 3 % de las solicitudes al punto de conexión https://example.net/api/experimental
y el 97 % de las solicitudes al punto de conexión https://example.net/api/stable
.IRoutingStrategyBuilder
para usar el método ConfigureWeightedGroups
para configurar elPara configurar un grupo ponderado, llame al método ConfigureWeightedGroups
en el tipo IRoutingStrategyBuilder
. En el siguiente ejemplo se configura el IRoutingStrategyBuilder
para usar el método ConfigureWeightedGroups
para configurar los grupos ponderados.
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 }
}
});
});
});
El código anterior:
IRoutingStrategyBuilder
para usar el método ConfigureWeightedGroups
para configurar los grupos ponderados.SelectionMode
en WeightedGroupSelectionMode.EveryAttempt
.WeightedEndpointGroup
al weightedGroup
que enruta el 33 % de las solicitudes al punto de conexión https://example.net/api/a
, el 33 % de las solicitudes al punto de conexión https://example.net/api/b
y el 33 % de las solicitudes al punto de conexión https://example.net/api/c
.Sugerencia
El número máximo de intentos de cobertura se correlaciona directamente con el número de grupos configurados. Por ejemplo, si tiene dos grupos, el número máximo de intentos es dos.
Para más información, consulte Documentación de Polly: Estrategia de resistencia de cobertura.
Es habitual configurar un grupo ordenado o un grupo ponderado, pero es válido configurar ambos. El uso de grupos ordenados y ponderados es útil en escenarios en los que desea enviar un porcentaje de las solicitudes a un punto de conexión diferente, como es el caso de las pruebas A/B.
Para tener más control, puede personalizar los controladores de resistencia mediante la API AddResilienceHandler
. Este método acepta un delegado que configura la instancia ResiliencePipelineBuilder<HttpResponseMessage>
que se usa para crear las estrategias de resistencia.
Para configurar un controlador de resistencia con nombre, llame al método de extensión AddResilienceHandler
con el nombre del controlador. En el siguiente ejemplo se configura un controlador de resistencia denominado "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));
});
El código anterior:
"CustomPipeline"
como el pipelineName
al contenedor de servicios.RequestTimeout
y TooManyRequests
al generador de resistencia.Hay muchas opciones disponibles para cada una de las estrategias de resistencia. Para obtener más información, consulte Documentación de Polly: Estrategias. Para obtener más información sobre la configuración de delegados ShouldHandle
, consulte Documentación de Polly: Control de errores en estrategias reactivas.
Polly admite la recarga dinámica de las estrategias de resistencia configuradas. Esto significa que puede cambiar la configuración de las estrategias de resistencia en tiempo de ejecución. Para habilitar la recarga dinámica, use la sobrecarga AddResilienceHandler
adecuada que expone el ResilienceHandlerContext
. Dado el contexto, llame a EnableReloads
de las opciones de estrategia de resistencia correspondientes:
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);
});
El código anterior:
"AdvancedPipeline"
como el pipelineName
al contenedor de servicios."AdvancedPipeline"
cada vez que cambian las opciones RetryStrategyOptions
con nombre.Para obtener más información, consulte Documentación de Polly: inserción de dependencias avanzadas.
Este ejemplo se basa en una sección de opciones que es capaz de cambiar, como un archivo appsettings.json. Fíjese en el siguiente archivo appsettings.json:
{
"RetryOptions": {
"Retry": {
"BackoffType": "Linear",
"UseJitter": false,
"MaxRetryAttempts": 7
}
}
}
Ahora imagine que estas opciones estaban enlazadas a la configuración de la aplicación, enlazando el HttpRetryStrategyOptions
a la sección "RetryOptions"
:
var section = builder.Configuration.GetSection("RetryOptions");
builder.Services.Configure<HttpStandardResilienceOptions>(section);
Para obtener más información, consulte Patrón de opciones en .NET.
La aplicación se basa en la inserción de dependencias para resolver y el ExampleClient
y su HttpClient correspondiente. El código compila el IServiceProvider y resuelve el ExampleClient
a partir de él.
IHost host = builder.Build();
ExampleClient client = host.Services.GetRequiredService<ExampleClient>();
await foreach (Comment? comment in client.GetCommentsAsync())
{
Console.WriteLine(comment);
}
El código anterior:
ExampleClient
a partir del IServiceProvider.GetCommentsAsync
en el ExampleClient
para obtener los comentarios.Imagine una situación en la que la red deja de funcionar o el servidor deja de responder. En el siguiente diagrama se muestra cómo las estrategias de resistencia controlarían la situación, dado el método ExampleClient
y el GetCommentsAsync
:
El diagrama anterior muestra:
ExampleClient
envía una solicitud HTTP GET al punto de conexión /comments
.Aunque se trata de un ejemplo sencillo, muestra cómo se pueden usar las estrategias de resistencia para controlar errores transitorios. Para obtener más información, consulte Documentación de Polly: Estrategias.
En las secciones siguientes se describen varios problemas conocidos.
Si usa Grpc.Net.ClientFactory
en su versión 2.63.0
o versiones anteriores, al habilitar los controladores de cobertura o resistencia estándar para un cliente gRPC podría generar una excepción en tiempo de ejecución. Fíjese en concreto en el siguiente ejemplo de código:
services
.AddGrpcClient<Greeter.GreeterClient>()
.AddStandardResilienceHandler();
El código anterior da como resultado la siguiente excepción:
System.InvalidOperationException: The ConfigureHttpClient method is not supported when creating gRPC clients. Unable to create client with name 'GreeterClient'.
Para resolver este problema, se recomienda actualizar Grpc.Net.ClientFactory
a la versión 2.64.0
o posterior.
Hay una comprobación del tiempo de compilación que verifica si usa Grpc.Net.ClientFactory
en su versión 2.63.0
o anterior y, si hay comprobación, se crea una advertencia de compilación. Puede suprimir la advertencia creando la siguiente propiedad en el archivo del proyecto:
<PropertyGroup>
<SuppressCheckGrpcNetClientFactoryVersion>true</SuppressCheckGrpcNetClientFactoryVersion>
</PropertyGroup>
Si usa Application Insights de .NET, habilitar la funcionalidad de resistencia en la aplicación podría hacer que falte toda la telemetría de Application Insights. El problema se produce cuando la funcionalidad de resistencia se registra antes que los servicios de Application Insights. Tenga en cuenta el ejemplo siguiente que causa el problema:
// 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();
El problema se debe al siguiente error en Application Insights y se puede corregir registrando los servicios de Application Insights antes que la funcionalidad de resistencia, como se muestra a continuación:
// We register Application Insights first, and now it will be working correctly.
services.AddApplicationInsightsTelemetry();
services.AddHttpClient().AddStandardResilienceHandler();
Comentarios de .NET
.NET es un proyecto de código abierto. Seleccione un vínculo para proporcionar comentarios:
Eventos
Compilación de Intelligent Apps
17 mar, 23 - 21 mar, 23
Únase a la serie de reuniones para crear soluciones de inteligencia artificial escalables basadas en casos de uso reales con compañeros desarrolladores y expertos.
Regístrese ahoraCursos
Módulo
Implementación de la resistencia en un microservicio de nativo de nube - Training
Este módulo le guía por la implementación de la resistencia en una aplicación de microservicios de .NET en un servicio de Kubernetes.
Documentación
Implementación de reintentos de llamada HTTP con retroceso exponencial con Polly - .NET
Obtenga información sobre cómo controlar los errores HTTP con Polly e IHttpClientFactory.
Uso de IHttpClientFactory para implementar solicitudes HTTP resistentes - .NET
Aprenda a usar IHttpClientFactory, disponible desde .NET Core 2.1, para crear instancias de "HttpClient", lo que facilita su uso en las aplicaciones.
Implementación del patrón de interruptor - .NET
Aprenda a implementar el patrón de interruptor como un sistema complementario en los reintentos HTTP.