Как сделать запасной вариант для автоматического выключателя, который вызывается при всех повторных попытках обрыва цепи

У меня есть следующие политики:

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 ответ

Теперь fallbackForCircuitBreaker вызывается только в случае сбоя всех повторных попыток и если последняя повторная попытка завершится неудачей с BrokenCircuitException. Какие изменения необходимо внести для того, чтобы fallbackForCircuitBreaker вызывался каждый раз при повторной попытке на разорванной цепи?

См. Документацию PolicyWrap, особенно схемы и описание работы. Политики в 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));

Могу ли я изменить политику повторных попыток, чтобы обрабатывать также массовые отклонения головки?

Документация переборки Полли заявляет, что броски политики BulkheadRejectedException, так:

var retryPolicy = Policy
    .Handle<HttpRequestException>()
    .Or<BrokenCircuitException>()
    .Or<BulkheadRejectedException>()
    /* etc */

Что в идеале должно быть сделано на onBulkheadRejectedAsync?

Вы можете войти. Вообще говоря, вы можете сбросить лишнюю нагрузку или использовать отклонение переборки в качестве триггера для горизонтального масштабирования для увеличения пропускной способности. Документация Polly обеспечивает более подробное обсуждение здесь и здесь.

Кроме того, я использую sharedBulkHead, который является полем экземпляра в службе и инициализируется в конструкторе. Это хорошая практика?

Это зависит. Время жизни экземпляра политики перегородки должно быть долгоживущим для управляемых вызовов, а не для каждого вызова, чтобы состояние экземпляра политики переборки могло управлять числом вызовов, выполняемых одновременно.

  • Если служба существует как долгоживущий одноэлементный, хранение политики переборки в поле экземпляра будет целесообразным, так как экземпляр политики переборки также будет долгоживущим.
  • Если экземпляры класса обслуживания создаются как переходные процессы / запросы по запросу контейнера DI, вам необходимо убедиться, что экземпляр политики переборки все еще является долгоживущим и используется совместно для одновременных запросов (например, делая его static), а не по запросу.
  • Если экземпляры переборки сконфигурированы для HttpClient с помощью HttpClientFactory, следуйте указаниям по определению объема политик с сохранением состояния с помощью HttpClientFactory в документации Polly и HttpClientFactory.
Другие вопросы по тегам