问题描述
有一个扩展方法将 IAccountSearchServiceClient
注册到一些策略处理程序,看起来像使用了 Polly 库
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]' 已被
注册。
来源=Microsoft.Extensions.DependencyInjection.Abstractions
sp.GetrequiredService<AccountSearchServiceClient>()
我对波莉不是很熟悉。是不是少了什么?
我在构造函数上放置了一个断点,但在 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.
,
问题
尽管拥有一个包含完整政策的注册表看起来很有吸引力,但不幸的是它很容易出错。乍一看,它似乎提供了极大的灵活性,让消费者能够组合他/她想要的任何策略。
但是有一个问题,与escalation有关。如果内部策略失败,那么 Polly 会将问题升级到下一个外部策略(在策略链中),该外部策略可能知道也可能不知道内部策略。
示例 #1
让我们继续使用您的示例,其中您有重试和断路器策略。
消费者可以按以下两个顺序进行注册:
- 断路器 >> 重试
- 重试>>断路器
- 在前一种情况下,重试不会抛出任何特定于 Polly 的异常。
- 但在后一种情况下,Polly 可能会抛出
BrokenCircuitException
。- 因此,根据用例,您的重试策略可能需要注意这一点。
示例 #2
假设您也有超时政策。这就是事情变得相当复杂的地方。
超时可以用作本地或全局超时:
- 重试>>断路器>>超时
- 超时 >> 重试 >> 断路器
+1。超时>>重试>>断路器>>超时
- 在第一种情况下,您的超时不应了解任何其他 Polly 策略,但断路器和重试可能需要了解
TimeoutRejectedException
。 - 在第二种情况下,您的超时可能需要注意
BrokenCircuitException
... - 在额外的情况下,您如何定义一个可以重复用于本地和全局超时的策略?
设计
幸运的是,Polly 可以帮助我们解决 Wrap。有了这个,您可以按给定的顺序显式地组合两个或多个策略。这有助于您定义策略,而不仅仅是个别政策。
因此,您可以公开组合了一组策略以实现所需行为的策略,而不是公开策略。
让我们继续重试 >> 断路器 >> 超时示例:
- 超时注意:-
- 断路器知道:
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);
}
}