使用 Polly 重试策略时如何处理 100 秒超时
How to deal with 100 seconds timeouts while using Poly retry policy
我在 .net 核心应用程序中使用重试策略,超过 100 秒后超时。
我可能以某种不正确的方式使用 Poly,或者它是设计使然,只有超时时间增加可能有帮助?
下面是我使用 Poly 的方式:
启动:
// populate timeouts array from appsettings
var resilencyOptions = services.BuildServiceProvider().GetRequiredService<IOptions<ResiliencyOptions>>().Value;
var attempts = resilencyOptions.TimeOutsInSeconds.Count;
TimeSpan[] timeouts = new TimeSpan[attempts];
int i = 0;
foreach (var timeout in resilencyOptions.TimeOutsInSeconds)
{
timeouts[i++] = TimeSpan.FromSeconds(timeout);
}
// register
services.AddTransient<LoggingDelegatingHandler>();
services.AddHttpClient<IMyClient, MyClient>()
.AddHttpMessageHandler<LoggingDelegatingHandler>()
.AddPolicyHandler(ResiliencyPolicy.GetRetryPolicy(attempts, timeouts))
.AddPolicyHandler(ResiliencyPolicy.GetCircuitBreakerPolicy());
图书馆:
/// <summary>
/// Resiliency policy.
/// </summary>
public class ResiliencyPolicy
{
/// <summary>
/// Get a retry policy.
/// </summary>
/// <param name="numberofAttempts"> Количество попыток.</param>
/// <param name="timeOfAttempts"> Массив с таймаутами между попытками, если передается неполный или пустой, попытки делаются в секундах 2^.</param>
/// <returns></returns>
public static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy(int numberofAttempts = 5, TimeSpan[] timeOfAttempts = null)
{
// In case timeOfAttempts is null or its elements count doesnt correspond to number of attempts provided,
// we will wait for:
// 2 ^ 1 = 2 seconds then
// 2 ^ 2 = 4 seconds then
// 2 ^ 3 = 8 seconds then
// 2 ^ 4 = 16 seconds then
// 2 ^ 5 = 32 seconds
return HttpPolicyExtensions
.HandleTransientHttpError()
.WaitAndRetryAsync(
retryCount: numberofAttempts,
sleepDurationProvider: retryAttempt => ((timeOfAttempts == null) || (timeOfAttempts.Length != numberofAttempts)) ?
TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)) :
timeOfAttempts[retryAttempt],
onRetry: (exception, retryCount, context) =>
{
Logging.Global.LogError($"Retry {retryCount} of {context.PolicyKey} at {context.OperationKey}, due to: {exception}.");
});
}
/// <summary>
/// Get circuit breaker policy.
/// </summary>
/// <param name="numberofAttempts">количество попыток</param>
/// <param name="durationOfBreaksInSeconds">количество секунд (таймаут) между попытками</param>
/// <returns></returns>
public static IAsyncPolicy<HttpResponseMessage> GetCircuitBreakerPolicy(int numberofAttempts = 5, int durationOfBreaksInSeconds = 30)
{
return HttpPolicyExtensions
.HandleTransientHttpError()
.CircuitBreakerAsync(
handledEventsAllowedBeforeBreaking: numberofAttempts,
durationOfBreak: TimeSpan.FromSeconds(durationOfBreaksInSeconds)
);
}
}
从自定义 http 客户端调用:
public class MyClient : IMyClient
{
private readonly HttpClient _httpClient;
private readonly ILogger<MyClient> _logger;
public MyClient(HttpClient httpClient, ILogger<MyClient> logger)
{
_httpClient = httpClient;
_logger = logger;
}
public async Task<bool> Notify(string url, Guid id, string orderId, int state, int category, DateTime date, CancellationToken cancellationToken)
{
// prepare request
var request = new
{
Id = id,
OrderId = orderId,
State = state,
Category = category,
Date = date
};
var data = new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, "application/json");
// send request
_logger.LogInformation("sending request to {url}", url);
var response = await _httpClient.PostAsync(url, data, cancellationToken);
// process response
if (response.IsSuccessStatusCode)
return true;
var content = await response.Content.ReadAsStringAsync(cancellationToken);
response.Content?.Dispose();
throw new HttpRequestException($"{response.ReasonPhrase}. {content.Replace("\"", "").TrimEnd()}", null, response.StatusCode);
}
}
控制器模拟端点可用性:
[ApiController]
[Route("[controller]")]
public class RabbitController : ControllerBase
{
private static int _numAttempts;
public RabbitController(IBus client)
{
_client = client;
}
[HttpPost("ProcessTestREST")]
public IActionResult ProcessTestREST(Object data)
{
_numAttempts++;
if (_numAttempts%4==3)
{
return Ok();
}
else
{
return StatusCode((int)HttpStatusCode.InternalServerError, "Something went wrong");
}
}
}
我收到此错误:“由于配置的 HttpClient.Timeout 已过 100 秒,请求被取消。”
var timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(10));
var longTimeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(30));
builder.Services.AddHttpClient("PollyDynamic")
.AddPolicyHandler(httpRequestMessage =>
httpRequestMessage.Method == HttpMethod.Get ? timeoutPolicy : longTimeoutPolicy);
应在 AddHttpClient 阶段设置超时策略以覆盖官方文档中定义的 100 秒默认值。
您的 polly 相关请求的超时应覆盖重试策略的最大值。
小心使用自定义客户端,以防您想忽略重试,这样超时是默认的。
您需要确保 HttpClient 的超时大于您的 Polly 策略的任何超时。您需要使用 AddHttpClient
重载,将客户端的默认超时从 100 秒更改。
var notFoundTimeout = TimeSpan.FromMinutes(5);
var transientTimeout = TimeSpan.FromSeconds(5);
var clientTimeout = notFoundTimeout.Add(new TimeSpan(0, 1, 0));
var notFoundRetryPolicy = Policy.Handle<HttpRequestException>() // 404 not found errors
.OrResult<HttpResponseMessage>(response => response.StatusCode == System.Net.HttpStatusCode.NotFound)
.WaitAndRetryAsync(3, (int tryIndex) => notFoundTimeout);
services.AddHttpClient(CLIENT_NAME, config => config.Timeout = clientTimeout)
.AddPolicyHandler(notFoundRetryPolicy)
.AddTransientHttpErrorPolicy(
builder => builder.WaitAndRetryAsync(3, (int tryIndex) => transientTimeout));
我在 .net 核心应用程序中使用重试策略,超过 100 秒后超时。 我可能以某种不正确的方式使用 Poly,或者它是设计使然,只有超时时间增加可能有帮助?
下面是我使用 Poly 的方式: 启动:
// populate timeouts array from appsettings
var resilencyOptions = services.BuildServiceProvider().GetRequiredService<IOptions<ResiliencyOptions>>().Value;
var attempts = resilencyOptions.TimeOutsInSeconds.Count;
TimeSpan[] timeouts = new TimeSpan[attempts];
int i = 0;
foreach (var timeout in resilencyOptions.TimeOutsInSeconds)
{
timeouts[i++] = TimeSpan.FromSeconds(timeout);
}
// register
services.AddTransient<LoggingDelegatingHandler>();
services.AddHttpClient<IMyClient, MyClient>()
.AddHttpMessageHandler<LoggingDelegatingHandler>()
.AddPolicyHandler(ResiliencyPolicy.GetRetryPolicy(attempts, timeouts))
.AddPolicyHandler(ResiliencyPolicy.GetCircuitBreakerPolicy());
图书馆:
/// <summary>
/// Resiliency policy.
/// </summary>
public class ResiliencyPolicy
{
/// <summary>
/// Get a retry policy.
/// </summary>
/// <param name="numberofAttempts"> Количество попыток.</param>
/// <param name="timeOfAttempts"> Массив с таймаутами между попытками, если передается неполный или пустой, попытки делаются в секундах 2^.</param>
/// <returns></returns>
public static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy(int numberofAttempts = 5, TimeSpan[] timeOfAttempts = null)
{
// In case timeOfAttempts is null or its elements count doesnt correspond to number of attempts provided,
// we will wait for:
// 2 ^ 1 = 2 seconds then
// 2 ^ 2 = 4 seconds then
// 2 ^ 3 = 8 seconds then
// 2 ^ 4 = 16 seconds then
// 2 ^ 5 = 32 seconds
return HttpPolicyExtensions
.HandleTransientHttpError()
.WaitAndRetryAsync(
retryCount: numberofAttempts,
sleepDurationProvider: retryAttempt => ((timeOfAttempts == null) || (timeOfAttempts.Length != numberofAttempts)) ?
TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)) :
timeOfAttempts[retryAttempt],
onRetry: (exception, retryCount, context) =>
{
Logging.Global.LogError($"Retry {retryCount} of {context.PolicyKey} at {context.OperationKey}, due to: {exception}.");
});
}
/// <summary>
/// Get circuit breaker policy.
/// </summary>
/// <param name="numberofAttempts">количество попыток</param>
/// <param name="durationOfBreaksInSeconds">количество секунд (таймаут) между попытками</param>
/// <returns></returns>
public static IAsyncPolicy<HttpResponseMessage> GetCircuitBreakerPolicy(int numberofAttempts = 5, int durationOfBreaksInSeconds = 30)
{
return HttpPolicyExtensions
.HandleTransientHttpError()
.CircuitBreakerAsync(
handledEventsAllowedBeforeBreaking: numberofAttempts,
durationOfBreak: TimeSpan.FromSeconds(durationOfBreaksInSeconds)
);
}
}
从自定义 http 客户端调用:
public class MyClient : IMyClient
{
private readonly HttpClient _httpClient;
private readonly ILogger<MyClient> _logger;
public MyClient(HttpClient httpClient, ILogger<MyClient> logger)
{
_httpClient = httpClient;
_logger = logger;
}
public async Task<bool> Notify(string url, Guid id, string orderId, int state, int category, DateTime date, CancellationToken cancellationToken)
{
// prepare request
var request = new
{
Id = id,
OrderId = orderId,
State = state,
Category = category,
Date = date
};
var data = new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, "application/json");
// send request
_logger.LogInformation("sending request to {url}", url);
var response = await _httpClient.PostAsync(url, data, cancellationToken);
// process response
if (response.IsSuccessStatusCode)
return true;
var content = await response.Content.ReadAsStringAsync(cancellationToken);
response.Content?.Dispose();
throw new HttpRequestException($"{response.ReasonPhrase}. {content.Replace("\"", "").TrimEnd()}", null, response.StatusCode);
}
}
控制器模拟端点可用性:
[ApiController]
[Route("[controller]")]
public class RabbitController : ControllerBase
{
private static int _numAttempts;
public RabbitController(IBus client)
{
_client = client;
}
[HttpPost("ProcessTestREST")]
public IActionResult ProcessTestREST(Object data)
{
_numAttempts++;
if (_numAttempts%4==3)
{
return Ok();
}
else
{
return StatusCode((int)HttpStatusCode.InternalServerError, "Something went wrong");
}
}
}
我收到此错误:“由于配置的 HttpClient.Timeout 已过 100 秒,请求被取消。”
var timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(10));
var longTimeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(30));
builder.Services.AddHttpClient("PollyDynamic")
.AddPolicyHandler(httpRequestMessage =>
httpRequestMessage.Method == HttpMethod.Get ? timeoutPolicy : longTimeoutPolicy);
应在 AddHttpClient 阶段设置超时策略以覆盖官方文档中定义的 100 秒默认值。
您的 polly 相关请求的超时应覆盖重试策略的最大值。
小心使用自定义客户端,以防您想忽略重试,这样超时是默认的。
您需要确保 HttpClient 的超时大于您的 Polly 策略的任何超时。您需要使用 AddHttpClient
重载,将客户端的默认超时从 100 秒更改。
var notFoundTimeout = TimeSpan.FromMinutes(5);
var transientTimeout = TimeSpan.FromSeconds(5);
var clientTimeout = notFoundTimeout.Add(new TimeSpan(0, 1, 0));
var notFoundRetryPolicy = Policy.Handle<HttpRequestException>() // 404 not found errors
.OrResult<HttpResponseMessage>(response => response.StatusCode == System.Net.HttpStatusCode.NotFound)
.WaitAndRetryAsync(3, (int tryIndex) => notFoundTimeout);
services.AddHttpClient(CLIENT_NAME, config => config.Timeout = clientTimeout)
.AddPolicyHandler(notFoundRetryPolicy)
.AddTransientHttpErrorPolicy(
builder => builder.WaitAndRetryAsync(3, (int tryIndex) => transientTimeout));