如何对在断路的所有重试中调用的断路器进行后备操作

时间:2018-12-27 11:14:05

标签: c# asp.net polly

我有以下政策:

var sharedBulkhead = Policy.BulkheadAsync(
            maxParallelization: maxParallelizations, 
            maxQueuingActions: maxQueuingActions,
            onBulkheadRejectedAsync: (context) =>
            {
                Log.Info($"Bulk head rejected => Policy Wrap: {context.PolicyWrapKey}, Policy: {context.PolicyKey}, Endpoint: {context.OperationKey}");
                return TaskHelper.EmptyTask;
            }
        );

var retryPolicy = Policy.Handle<HttpRequestException>()
.Or<BrokenCircuitException>().WaitAndRetryAsync(
            retryCount: maxRetryCount,
            sleepDurationProvider: attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt)),
            onRetryAsync: (exception, calculatedWaitDuration, retryCount, context) =>
            {
                Log.Error($"Retry => Count: {retryCount}, Wait duration: {calculatedWaitDuration}, Policy Wrap: {context.PolicyWrapKey}, Policy: {context.PolicyKey}, Endpoint: {context.OperationKey}, Exception: {exception}.");
                return TaskHelper.EmptyTask;
            });

            var circuitBreaker = Policy.Handle<Exception>(e => (e is HttpRequestException)).CircuitBreakerAsync(
            exceptionsAllowedBeforeBreaking: maxExceptionsBeforeBreaking, 
            durationOfBreak: TimeSpan.FromSeconds(circuitBreakDurationSeconds), 
            onBreak: (exception, timespan, context) =>
            {
                Log.Error($"Circuit broken => Policy Wrap: {context.PolicyWrapKey}, Policy: {context.PolicyKey}, Endpoint: {context.OperationKey}, Exception: {exception}");
            },
            onReset: (context) =>
            {
                Log.Info($"Circuit reset => Policy Wrap: {context.PolicyWrapKey}, Policy: {context.PolicyKey}, Endpoint: {context.OperationKey}");
            }
        );

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

var fallbackForAnyException = Policy<bool>
            .Handle<Exception>()
            .FallbackAsync(
                fallbackAction: (ct, context) => { return Task.FromResult(false); },
                onFallbackAsync: (e, context) =>
                {
                    Log.Error($"An unexpected error occured => Policy Wrap: {context.PolicyWrapKey}, Policy: {context.PolicyKey}, Endpoint: {context.OperationKey}");
                    return TaskHelper.EmptyTask;
                }
            );


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

现在,fallbackForCircuitBreaker仅在所有重试均失败且上一次重试失败并BrokenCircuitException时调用。为了使fallbackForCircuitBreaker每次在断路时重试都被调用,应该进行哪些更改?

此外,我正在使用sharedBulkHead,它是服务中的实例字段,并在构造函数中初始化。这是一个好习惯吗?理想情况下,在onBulkheadRejectedAsync上应该做什么?我可以修改重试策略来处理批量头拒绝吗?

1 个答案:

答案 0 :(得分:0)

  

现在,仅当所有重试均失败且最后一次重试失败并带有BrokenCircuitException时,才调用fallbackForCircuitBreaker。为了使每次在断路电路上重试时调用fallbackForCircuitBreaker,应该进行哪些更改?

请参见PolicyWrap documentation,尤其是diagrams and description of operation。 PolicyWrap中的策略就像调用周围的一系列嵌套中间件一样:

  • 最外层(按读取顺序最左)策略执行下一个内层,然后执行下一个内层,依此类推;直到最里面的策略执行用户委托为止;
  • 抛出的异常通过各层向外冒泡(直到处理)

因此,要使每次尝试调用fallbackForCircuitBreaker(相当于)fallbackForCircuitBreaker,请将其移到重试策略中。

但是,当前的false将抛出的异常替换为返回值var sharedBulkhead = /* as before */; var retryPolicy = /* as before */; var fallbackForCircuitBreaker = /* as before */; var logCircuitBreakerBrokenPerTry = Policy<bool> .Handle<BrokenCircuitException>() .FallbackAsync( fallbackValue: false, onFallbackAsync: (outcome, context) => { Log.Error($"Operation attempted on broken circuit => Policy Wrap: {context.PolicyWrapKey}, Policy: {context.PolicyKey}, Endpoint: {context.OperationKey}"); throw outcome.Exception; } ); var fallbackForAnyException = /* as before */; var resilienceStrategy = Policy.WrapAsync(retryPolicy, logCircuitBreakerBrokenPerTry, circuitBreaker, sharedBulkhead); var policyWrap = fallbackForAnyException.WrapAsync(fallbackForCircuitBreaker.WrapAsync(resilienceStrategy)); ,而这听起来像是您从每次尝试回退中寻求的是“日志,然后进行下一次尝试” 。这样做的技术是使用后备作为log then rethrow,以便您的重试策略仍然可以响应(重新抛出)异常。所以:

BulkheadRejectedException

  

我可以修改重试策略来处理批量拒绝吗?

Polly bulkhead documentation声明策略抛出var retryPolicy = Policy .Handle<HttpRequestException>() .Or<BrokenCircuitException>() .Or<BulkheadRejectedException>() /* etc */ ,所以:

static
  

理想情况下在onBulkheadRejectedAsync上要做什么?

您可以登录。广义上讲,您可以减轻多余的负载,也可以将隔板抑制作为触发来水平扩展以增加容量。 Polly文档提供了更多讨论herehere

  

此外,我正在使用sharedBulkHead,它是服务中的实例字段,并在构造函数中初始化。这是一个好习惯吗?

这取决于。 Bulkhead策略实例的生存期必须为long-lived across the governed calls,而不是每个调用,以确保Bulkhead策略实例的状态控制并发执行的调用数。

  • 如果该服务作为长期存在的单例存在,则将隔板策略保留在实例字段中将是适当的,因为隔板策略实例也将具有较长的寿命。
  • 如果服务类的实例是由DI容器作为临时/按请求创建的,则您需要确保隔板策略实例仍然有效并在并发请求之间共享(例如,使其成为{{1} }),而不是按请求。
  • 如果通过HttpClientFactory在HttpClient上配置了隔板的实例,请遵循scoping stateful policies with HttpClientFactory in the Polly and HttpClientFactory documentation上的说明。