有一个扩展方法可以用一些策略处理程序注册IAccountSearchServiceClient
,看起来像是使用了Polly lib
public static IServiceCollection AddAccountSearchServiceClient(this IServiceCollection services)
{
services.AddHttpClient<AccountSearchServiceClient>()
.ConfigureHttpClient((sp, client) =>
{
var options = sp.GetRequiredService<IOptions<AccountSearchSettings>>();
var settings = options.Value;
client.BaseAddress = settings.BaseUrl;
})
.ConfigurePrimaryHttpMessageHandler(() =>
{
var handler = new HttpClientHandler
{
ClientCertificateOptions = ClientCertificateOption.Manual,
ServerCertificateCustomValidationCallback = (m, c, cc, pe) => true
};
return handler;
})
.AddPolicyHandler(request => request.Method == HttpMethod.Get ? Policies.ShortTimeout : Policies.LongTimeout)
.AddPolicyHandlerFromRegistry("circuitBreaker")
.AddPolicyHandlerFromRegistry("bulkhead")
.AddPolicyHandlerFromRegistry("RetryPolicy");
services.AddScoped<IAccountSearchServiceClient>(sp => sp.GetRequiredService<AccountSearchServiceClient>());
return services;
}
在运行时得到这样一个DI错误:
System.InvalidOperationException HResult=0x80131509消息=否类型服务"Polly.Registry.IReadOnlyPolicyRegistry `1[System.String]"已被已注册
Source=Microsoft.Extensions.DependencyInjection.Abstractions
此处发生错误
sp.GetRequiredService<AccountSearchServiceClient>()
我对波利不是很熟悉。有什么东西不见了吗
我在构造函数上设置了一个断点,但未调用ctor。错误发生在ConfigurePrimaryHttpMessageHandler
之后
构造函数如下所示:
public AccountSearchServiceClient(HttpClient httpClient, IOptions<AccountSearchSettings> settings)
{
_httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
_settings = settings?.Value ?? throw new ArgumentNullException(nameof(settings));
}
没有直接注射或使用IReadOnlyPolicyRegistry
,我想这是Polly 的内部类型
原来缺少的部分是策略注册:
var policyRegistry = services.AddPolicyRegistry();
policyRegistry["circuitBreaker"] = HttpPolicyExtensions.HandleTransientHttpError()
.CircuitBreakerAsync(5, TimeSpan.FromSeconds(20));
policyRegistry["RetryPolicy"] = ...;
//etc.
问题
尽管拥有一个充满策略的注册表似乎很有吸引力,但不幸的是,它很容易出错。乍一看,它似乎提供了很大的灵活性,允许消费者能够结合他/她想要的任何政策。
但有一个问题,与局势升级有关。如果内部策略失败,Polly将把问题升级到下一个外部策略(在策略链中(,该策略可能知道也可能不知道内部策略。
示例#1
让我们以您的例子为例,您有"重试"one_answers"断路器"策略
消费者可以在以下两个订单中进行注册:
- 断路器>gt;重试
- 重试>gt;断路器
- 在前一种情况下,重试不会引发任何Polly特定的异常
- 但在后一种情况下,波利可能会抛出
BrokenCircuitException
。- 因此,根据使用情况,您的重试策略可能需要注意这一点
示例#2
假设您也有一个超时策略。这就是事情变得相当复杂的地方
超时可以用作本地或全局超时:
- 重试>gt;断路器>gt;超时
- 超时>gt;重试>gt;断路器
+1.超时>gt;重试>gt;断路器>gt;超时
- 在第一种情况下,您的Timeout不应该知道任何其他Polly策略,但Circuit Breaker和Retry可能需要知道
TimeoutRejectedException
- 在第二种情况下,您的Timeout可能需要注意
BrokenCircuitException
- 在奖金的情况下,如何定义一个可以在本地和全局超时时重复使用的策略
设计
幸运的是,波利可以帮我们做Wrap。有了它,您可以按给定的顺序显式地组合两个或多个策略。这有助于您定义策略,而不仅仅是单个策略。
因此,您可以公开策略,而不是公开策略,这些策略结合了一组策略来实现所需的行为。
让我们继续使用"重试">gt;断路器>gt;超时示例:
- 超时意识到:-
- 断路器意识到:
HttpRequestExcetion
、TimeotRejectedException
- 重试注意:
HttpRequestExcetion
、TimeotRejectedException
、BrokenCircuitException
解决方案
让我们假设我们有以下抽象来指定上述三个策略的参数:
public class ResilienceSettings
{
public string BaseAddress { get; set; }
public int HttpRequestTimeoutInMilliseconds { get; set; }
public int HttpRequestRetrySleepDurationInMilliseconds { get; set; }
public int HttpRequestRetryCount { get; set; }
public int HttpRequestCircuitBreakerFailCountInCloseState { get; set; }
public int HttpRequestCircuitBreakerDelayInMillisecondsBetweenOpenAndHalfOpenStates { get; set; }
}
现在,让我们看看政策:
private static IAsyncPolicy<HttpResponseMessage> TimeoutPolicy(ResilienceSettings settings)
=> Policy
.TimeoutAsync<HttpResponseMessage>( //Catches TaskCanceledException and throws instead TimeoutRejectedException
timeout: TimeSpan.FromMilliseconds(settings.HttpRequestTimeoutInMilliseconds));
private static IAsyncPolicy<HttpResponseMessage> CircuitBreakerPolicy(ResilienceSettings settings)
=> HttpPolicyExtensions
.HandleTransientHttpError() //Catches HttpRequestException or checks the status code: 5xx or 408
.Or<TimeoutRejectedException>() //Catches TimeoutRejectedException, which can be thrown by an inner TimeoutPolicy
.CircuitBreakerAsync( //Monitors consecutive failures
handledEventsAllowedBeforeBreaking: settings
.HttpRequestCircuitBreakerFailCountInCloseState, //After this amount of consecutive failures it will break
durationOfBreak: TimeSpan.FromMilliseconds(settings
.HttpRequestCircuitBreakerDelayInMillisecondsBetweenOpenAndHalfOpenStates)); //After this amount of delay it will give it a try
private static IAsyncPolicy<HttpResponseMessage> RetryPolicy(ResilienceSettings settings)
=> HttpPolicyExtensions
.HandleTransientHttpError() //Catches HttpRequestException or checks the status code: 5xx or 408
.Or<BrokenCircuitException>() //Catches BrokenCircuitException, so whenever the broker is open then it refuses new requests
.Or<TimeoutRejectedException>() //Catches TimeoutRejectedException, which can be thrown by an inner TimeoutPolicy
.WaitAndRetryAsync( //Monitors the above anomalies
retryCount: settings.HttpRequestRetryCount, //After (this amount + 1) attempts it gives up
sleepDurationProvider: _ =>
TimeSpan.FromMilliseconds(settings.HttpRequestRetrySleepDurationInMilliseconds)); //After a failed attempt it delays the next try with this amount of time
最后是注册的扩展方法:
public static class ResilientHttpClientRegister
{
public static IServiceCollection AddXYZResilientStrategyToHttpClientProxy<TInterface, TImplementation>
(this IServiceCollection services, ResilienceSettings settings)
where TInterface: class
where TImplementation: class, TInterface
{
var (serviceUri, combinedPolicy) = CreateParametersForXYZStrategy<TInterface>(settings);
services.AddHttpClient<TInterface, TImplementation>(
client => { client.BaseAddress = serviceUri; })
.AddPolicyHandler(combinedPolicy); //Retry > Circuit Breaker > Timeout (outer > inner)
return services;
}
private static (Uri, IAsyncPolicy<HttpResponseMessage>) CreateParametersForXYZStrategy<TInterface>(ResilienceSettings settings)
{
Uri serviceUri = Uri.TryCreate(settings.BaseAddress, UriKind.Absolute, out serviceUri)
? serviceUri
: throw new UriFormatException(
$"Invalid url was set for the '{typeof(TInterface).Name}' resilient http client. " +
$"Its value was '{HttpUtility.UrlEncode(settings.BaseAddress)}'");
var combinedPolicy = Policy.WrapAsync(RetryPolicy(settings), CircuitBreakerPolicy(settings), TimeoutPolicy(settings));
return (serviceUri, combinedPolicy);
}
}