尚未注册第 'Polly.Registry.IReadOnlyPolicyRegistry`1[System.String]' 类服务



有一个扩展方法可以用一些策略处理程序注册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"断路器"策略
消费者可以在以下两个订单中进行注册:

  1. 断路器>gt;重试
  2. 重试>gt;断路器
  • 在前一种情况下,重试不会引发任何Polly特定的异常
  • 但在后一种情况下,波利可能会抛出BrokenCircuitException
    • 因此,根据使用情况,您的重试策略可能需要注意这一点

示例#2

假设您也有一个超时策略。这就是事情变得相当复杂的地方
超时可以用作本地或全局超时:

  1. 重试>gt;断路器>gt;超时
  2. 超时>gt;重试>gt;断路器

+1.超时>gt;重试>gt;断路器>gt;超时

  • 在第一种情况下,您的Timeout不应该知道任何其他Polly策略,但Circuit Breaker和Retry可能需要知道TimeoutRejectedException
  • 在第二种情况下,您的Timeout可能需要注意BrokenCircuitException
  • 在奖金的情况下,如何定义一个可以在本地和全局超时时重复使用的策略

设计

幸运的是,波利可以帮我们做Wrap。有了它,您可以按给定的顺序显式地组合两个或多个策略。这有助于您定义策略,而不仅仅是单个策略。

因此,您可以公开策略,而不是公开策略,这些策略结合了一组策略来实现所需的行为。

让我们继续使用"重试">gt;断路器>gt;超时示例:

  1. 超时意识到:-
  2. 断路器意识到:HttpRequestExcetionTimeotRejectedException
  3. 重试注意:HttpRequestExcetionTimeotRejectedExceptionBrokenCircuitException

解决方案

让我们假设我们有以下抽象来指定上述三个策略的参数:

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);
}
}

相关内容

最新更新