Как сделать запасной вариант для автоматического выключателя, который вызывается при всех повторных попытках обрыва цепи
У меня есть следующие политики:
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.