C# Polly断路器后退不工作

C# Polly断路器后退不工作,c#,asp.net,polly,C#,Asp.net,Polly,我有以下政策: var retryPolicy = Policy.Handle<Exception>(e => (e is HttpRequestException || e.InnerException is HttpRequestException)).WaitAndRetry( retryCount: maxRetryCount, sleepDurationProvider: attempt => Ti

我有以下政策:

var retryPolicy = Policy.Handle<Exception>(e => (e is HttpRequestException || e.InnerException is HttpRequestException)).WaitAndRetry(
                retryCount: maxRetryCount,
                sleepDurationProvider: attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt)),
                onRetry: (exception, calculatedWaitDuration, retryCount, context) =>
                {
                    Log.Error($"Retry => Count: {retryCount}, Wait duration: {calculatedWaitDuration}, Policy Wrap: {context.PolicyWrapKey}, Policy: {context.PolicyKey}, Endpoint: {context.OperationKey}, Exception: {exception}.");
                });

var circuitBreaker = Policy.Handle<Exception>(e => (e is HttpRequestException || e.InnerException is HttpRequestException)).CircuitBreaker(maxExceptionsBeforeBreaking, TimeSpan.FromSeconds(circuitBreakDurationSeconds), onBreak, onReset);

var sharedBulkhead = Policy.Bulkhead(maxParallelizations, maxQueuingActions, onBulkheadRejected);

var fallbackForCircuitBreaker = Policy<bool>
             .Handle<BrokenCircuitException>()
             .Fallback(
                 fallbackValue: false,
                 onFallback: (b, context) =>
                 {
                     Log.Error($"Operation attempted on broken circuit => Policy Wrap: {context.PolicyWrapKey}, Policy: {context.PolicyKey}, Endpoint: {context.OperationKey}");
                 }
             );

            var fallbackForAnyException = Policy<bool>
                .Handle<Exception>()
                .Fallback(
                    fallbackAction: (context) => { return false; },
                    onFallback: (e, context) =>
                    {
                        Log.Error($"An unexpected error occured => Policy Wrap: {context.PolicyWrapKey}, Policy: {context.PolicyKey}, Endpoint: {context.OperationKey}");
                    }
                );

var resilienceStrategy = Policy.Wrap(retryPolicy, circuitBreaker, sharedBulkhead);
            var policyWrap = fallbackForAnyException.Wrap(fallbackForCircuitBreaker.Wrap(resilienceStrategy));

public bool CallApi(ChangeMapModel changeMessage)
    {
        var httpClient = new HttpClient();
        var endPoint = changeMessage.EndPoint;
        var headers = endPoint.Headers;
        if (headers != null)
        {
            foreach (var header in headers)
            {
                if (header.Contains(':'))
                {
                    var splitHeader = header.Split(':');
                    httpClient.DefaultRequestHeaders.Add(splitHeader[0], splitHeader[1]); 
                }
            } 
        }

        var res = httpClient.PostAsync(endPoint.Uri, null);
        var response = res.Result;
        response.EnsureSuccessStatusCode();
        return true;
    }
问题是,当在断路器上执行操作时,断路器回调没有命中

我希望通过策略放置一个API调用,要处理的异常类型为
HttpRequestException
。政策定义有问题吗?为什么不调用断路器回退功能?

我创建了以下内容来帮助探索问题:

注:不一定是成品;只是对发布的代码进行一些小修改和额外的注释,以帮助探索这个问题

因此,这不属于张贴代码中的一个深度检查。但是,请注意,
CircuitBreakerException
包含导致电路中断的异常作为其
InnerException
。这可能会导致handle子句仅检查
e。InnerException是HttpRequestException
,如果出现以下情况之一,则会意外(如果不是您的目标,则会意外)重试
电路断路器异常

(a) 代码更改为
async
/
await
,这将删除
aggregateeexception
,从而导致嵌套只有一个深度

(b) 代码被更改为Polly的,这是递归贪婪的,因此将捕获嵌套的两个深度
CircuitBreakerException->AggregateException->HttpRequestException


建议
/*注释掉*///并在上面的代码中附加解释
,建议如何调整所发布的代码,以便
Fallbackforce断路器
按预期调用


还有两个想法:

  • 如果可能的话,考虑在整个过程中更改为
    async
    /
    wait
  • 通过调用
    .Result
    阻塞
    HttpClient.DoSomethingAsync()
    ,可能会影响性能,如果与其他异步代码混合,则可能导致死锁,并带来整个
    aggregateeexception
    -和-
    InnerException
    痛苦

  • 考虑
    HttpClient
    实例的处置和生存期
  • (有意将这些第3点和第4点保持简短,正如其他地方广泛讨论的那样。)

    我创建了以下内容来帮助探讨这个问题:

    注:不一定是成品;只是对发布的代码进行一些小修改和额外的注释,以帮助探索这个问题

    因此,这不属于张贴代码中的一个深度检查。但是,请注意,
    CircuitBreakerException
    包含导致电路中断的异常作为其
    InnerException
    。这可能会导致handle子句仅检查
    e。InnerException是HttpRequestException
    ,如果出现以下情况之一,则会意外(如果不是您的目标,则会意外)重试
    电路断路器异常

    (a) 代码更改为
    async
    /
    await
    ,这将删除
    aggregateeexception
    ,从而导致嵌套只有一个深度

    (b) 代码被更改为Polly的,这是递归贪婪的,因此将捕获嵌套的两个深度
    CircuitBreakerException->AggregateException->HttpRequestException


    建议
    /*注释掉*///并在上面的代码中附加解释
    ,建议如何调整所发布的代码,以便
    Fallbackforce断路器
    按预期调用


    还有两个想法:

  • 如果可能的话,考虑在整个过程中更改为
    async
    /
    wait
  • 通过调用
    .Result
    阻塞
    HttpClient.DoSomethingAsync()
    ,可能会影响性能,如果与其他异步代码混合,则可能导致死锁,并带来整个
    aggregateeexception
    -和-
    InnerException
    痛苦

  • 考虑
    HttpClient
    实例的处置和生存期

  • (有意将第3点和第4点保持简短,正如其他地方广泛讨论的那样。)

    您可以显示
    CallApi(…)
    方法的内容吗?或者提供一个stub/replacement
    CallApi(…)
    方法,将整个过程转化为一个可验证的示例。在这里另外,我在答案中添加了
    CallApi
    的代码。我特别怀疑我的策略定义,特别是重试策略和断路器策略处理的异常组合。感谢发布
    CallApi()
    方法。我能够根据发布的代码创建一个注释。您的确切原因可能取决于您选择的特定配置参数,但希望完整注释的示例提供足够的途径进行探索。您是否可以显示
    CallApi(…)
    方法的内容?或者提供一个stub/replacement
    CallApi(…)
    方法,将整个过程转化为一个可验证的示例。在这里另外,我在答案中添加了
    CallApi
    的代码。我特别怀疑我的策略定义,特别是重试策略和断路器策略处理的异常组合。感谢发布
    CallApi()
    方法。我能够根据发布的代码创建一个注释。您的确切原因可能取决于您选择的特定配置参数,但希望完整注释的示例提供了足够的途径进行探索。非常感谢您的回答。这很有帮助。“无意中重试断路器异常”-即使电路断开,我们是否仍要重试?“即使电路断开,我们是否仍要重试?”->同意。这实际上可能意味着您希望将重试切换到
    .HandleInner()
    ,或者显式添加
    .or()
    ,\u如果
    async
    /
    wait
    未使用。我决定切换到异步策略,这给我带来了问题。我为这一点补充了一个单独的问题。你介意看一下吗?非常感谢
    policyWrap.Execute((context) => CallApi(changeMessage), new Context(endPoint));
    
    using Polly;
    using Polly.CircuitBreaker;
    using System;
    using System.Net.Http;
    using System.Threading.Tasks;
    
    public class Program
    {
        public static void Main()
        {
            int maxRetryCount = 6;
            double circuitBreakDurationSeconds = 0.2 /* experiment with effect of shorter or longer here, eg: change to = 1, and the fallbackForCircuitBreaker is correctly invoked */ ;
            int maxExceptionsBeforeBreaking = 4; /* experiment with effect of fewer here, eg change to = 1, and the fallbackForCircuitBreaker is correctly invoked */
            int maxParallelizations = 2;
            int maxQueuingActions = 2;
    
            var retryPolicy = Policy.Handle<Exception>(e => (e is HttpRequestException || (/*!(e is BrokenCircuitException) &&*/ e.InnerException is HttpRequestException))) // experiment with introducing the extra (!(e is BrokenCircuitException) && ) clause here, if necessary/desired, depending on goal
                .WaitAndRetry(
                    retryCount: maxRetryCount,
                    sleepDurationProvider: attempt => TimeSpan.FromMilliseconds(50 * Math.Pow(2, attempt)),
                    onRetry: (ex, calculatedWaitDuration, retryCount, context) =>
                    {
                        Console.WriteLine(String.Format("Retry => Count: {0}, Wait duration: {1}, Policy Wrap: {2}, Policy: {3}, Endpoint: {4}, Exception: {5}", retryCount, calculatedWaitDuration, context.PolicyWrapKey, context.PolicyKey, context.OperationKey, ex.Message));
                    });
    
            var circuitBreaker = Policy.Handle<Exception>(e => (e is HttpRequestException || e.InnerException is HttpRequestException))
                .CircuitBreaker(maxExceptionsBeforeBreaking,
                    TimeSpan.FromSeconds(circuitBreakDurationSeconds),
                    onBreak: (ex, breakDuration) => {
                        Console.WriteLine(String.Format("Circuit breaking for {0} ms due to {1}", breakDuration.TotalMilliseconds, ex.Message));
                    },
                    onReset: () => {
                        Console.WriteLine("Circuit closed again.");
                    },
                    onHalfOpen: () => { Console.WriteLine("Half open."); });
    
            var sharedBulkhead = Policy.Bulkhead(maxParallelizations, maxQueuingActions);
    
            var fallbackForCircuitBreaker = Policy<bool>
                 .Handle<BrokenCircuitException>()
                /* .OrInner<BrokenCircuitException>() */ // Consider this if necessary.
                /* .Or<Exception>(e => circuitBreaker.State != CircuitState.Closed) */ // This check will also detect the circuit in anything but healthy state, regardless of the final exception thrown.
                 .Fallback(
                     fallbackValue: false,
                     onFallback: (b, context) =>
                     {
                         Console.WriteLine(String.Format("Operation attempted on broken circuit => Policy Wrap: {0}, Policy: {1}, Endpoint: {2}", context.PolicyWrapKey, context.PolicyKey, context.OperationKey));
                     }
                 );
    
            var fallbackForAnyException = Policy<bool>
                    .Handle<Exception>()
                    .Fallback<bool>(
                        fallbackAction: (context) => { return false; },
                        onFallback: (e, context) =>
                        {
                            Console.WriteLine(String.Format("An unexpected error occured => Policy Wrap: {0}, Policy: {1}, Endpoint: {2}, Exception: {3}", context.PolicyWrapKey, context.PolicyKey, context.OperationKey, e.Exception.Message));
                        }
                    );
    
            var resilienceStrategy = Policy.Wrap(retryPolicy, circuitBreaker, sharedBulkhead);
            var policyWrap = fallbackForAnyException.Wrap(fallbackForCircuitBreaker.Wrap(resilienceStrategy));
    
            bool outcome = policyWrap.Execute((context) => CallApi("http://www.doesnotexistattimeofwriting.com/"), new Context("some endpoint info"));
        }
    
        public static bool CallApi(string uri)
        {
            using (var httpClient = new HttpClient() { Timeout = TimeSpan.FromSeconds(1) }) // Consider HttpClient lifetimes and disposal; this pattern is for minimum change from original posted code, not a recommendation.
            {
                Task<HttpResponseMessage> res = httpClient.GetAsync(uri);
                var response = res.Result; // Consider async/await rather than blocking on the returned Task.
                response.EnsureSuccessStatusCode();
                return true;
            }
        }
    }
    
    CircuitBreakerException -> AggregateException -> HttpRequestException