没有注册“Polly.Registry.IReadOnlyPolicyRegistry`1[System.String]”类型的服务 示例 #1示例 #2

问题描述

一个扩展方法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

让我们继续使用您的示例,其中您有重试和断路器策略。
消费者可以按以下两个顺序进行注册:

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

示例 #2

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

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

+1。超时>>重试>>断路器>>超时

  • 在第一种情况下,您的超时不应了解任何其他 Polly 策略,但断路器和重试可能需要了解 TimeoutRejectedException
  • 在第二种情况下,您的超时可能需要注意 BrokenCircuitException...
  • 在额外的情况下,您如何定义一个可以重复用于本地和全局超时的策略?

设计

幸运的是,Polly 可以帮助我们解决 Wrap。有了这个,您可以按给定的顺序显式地组合两个或多个策略。这有助于您定义策略,而不仅仅是个别政策。

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

让我们继续重试 >> 断路器 >> 超时示例:

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