Платформа Polly CircuitBreakerAsync не повторяется в случае возникновения исключения
Я использую платформу Полли для временной обработки ошибок. Для синхронных операций политика автоматического выключателя Polly работает нормально, но когда я создал ее асинхронную версию, она не повторяет выполнение. Просьба предложить:
Асинхронный метод:
private async static Task HelloWorld()
{
if (DateTime.Now < programStartTime.AddSeconds(10))
{
Console.WriteLine("Task Failed.");
throw new TimeoutException();
}
await Task.Delay(TimeSpan.FromSeconds(1));
Console.WriteLine("Task Completed.");
}
Политика асинхронного автоматического выключателя Polly:
private static void AsyncDemo3(Func<Task> action)
{
programStartTime = DateTime.Now;
Policy policy = Policy
.Handle<TimeoutException>()
.CircuitBreakerAsync(3, TimeSpan.FromSeconds(2));
try
{
var a = policy.ExecuteAndCaptureAsync(action, true).GetAwaiter().GetResult();
}
catch (AggregateException ex)
{
Console.WriteLine("Exception: " + ex.Message);
}
catch (Exception ex)
{
Console.WriteLine("Exception: " + ex.Message);
}
}
Выполнение политики автоматического выключателя Polly:
AsyncDemo3 (HelloWorld);
Пожалуйста, помогите найти и решить проблему.
3 ответа
Я полагаю, вы не поняли, что делает политика выключателя.
Что он делает, так это то, что если вы вызываете его определенное количество раз, и он каждый раз терпит неудачу, то он перестанет вызывать данный метод на определенное время. Но это не повторяет само по себе.
Таким образом, чтобы сделать то, что я думаю, вы хотите сделать, вам нужно объединить политику повторных попыток с политикой автоматического выключателя. Один из способов сделать это будет:
Policy retryPolicy = Policy.Handle<TimeoutException>().RetryAsync(3);
Policy circuitBreakerPolicy = Policy
.Handle<TimeoutException>()
.CircuitBreakerAsync(3, TimeSpan.FromSeconds(2));
try
{
retryPolicy.ExecuteAsync(() => circuitBreakerPolicy.ExecuteAsync(action, true))
.GetAwaiter().GetResult();
}
…
Выход этого кода:
Task Failed.
Task Failed.
Task Failed.
Exception: The circuit is now open and is not allowing calls.
Ответы svick и user3613932 показывают только половину истории. А именно, как выполнить операцию несколько раз, чтобы разомкнуть выключатель. Но что нам делать дальше?
Попробую ответить на этот вопрос в этом посте.
Автоматический выключатель в целом
Вы должны рассматривать CB как прокси. Это позволяет пройти каждому запросу, если нижестоящая система считается исправной. Если CB обнаружит временный сбой, изучив последующие ответы (если таковые имеются), он сократит новые запросы, выдав исключение.
И здесь наступает важная часть: он будет блокировать новые запросы только на определенное время . Если это время истекло, то он позволяет одному запросу достичь нижестоящей системы в качестве зонда :
- Если это не удается, он снова сокращает все новые запросы за заданный период времени.
- В случае успеха он позволяет всем новым запросам достигать нижестоящей системы.
Таким образом, другими словами, прерыватель цепи — это шаблон, позволяющий избежать перегрузки нижестоящей системы (которая считается либо перегруженной, либо временно недоступной), пока она снова не станет работоспособной/доступной.
Пересмотр предлагаемых повторных попыток
Давайте сохраним ваше определение прерывателя цепи, поэтому после 3 последовательных s он должен отклонять все новые запросы на 2 секунды.
Для обоих вышеупомянутых членов SO предлагается эта политика повторных попыток:
Policy.Handle<TimeoutException>().RetryAsync(3);
3 попытки означают 4 попытки (1 начальная и 3 повторные). Потому что CB был настроен таким образом, что он должен открываться после 3 последовательныхTimeoutException
поэтому при 4-й попытке ЦБ выкинет .
Потому что нет политики, которая должна срабатывать, поэтомуExecuteAsync
бросает это исключение вызывающей стороне.
Давайте изменим политику повторных попыток, чтобы она срабатывала и для этого исключения, и изменим retryCount с 3 на 6.
Policy.Handle<TimeoutException>().Or<BrokenCircuitException>().RetryAsync(6);
Теперь политика срабатывает дляBrokenCircuitException
также, но CB сокращает 4 наших повторных попытки. Почему? Потому что ЦБ открыт на 2 секунды и запрещает все повторные попытки. (попытка может быть отклонена всего за несколько миллисекунд).
Таким образом, лучшим решением было бы подождать между каждой попыткой достижения, а не повторять попытку немедленно .
Policy.Handle<TimeoutException>()
.Or<BrokenCircuitException>()
.WaitAndRetryAsync(6, _ => TimeSpan.FromSeconds(1));
При таком подходе мы повторяем дольше, чем продолжительность перерыва CB. Другими словами, теперь одна из наших повторных попыток является зондом , который может быть успешным. И это общая цель введения логики отказоустойчивости для преодоления временных сбоев.
Продвигая основную идею
Я уже разместил на StackOverflow много примеров кода о Circuit Breaker.
Позвольте мне поделиться с вами наиболее актуальными здесь:
Рекомендуем создать обе политики и объединить их с помощью PolicyWrap следующим образом.
Создание Политики
var circuitBreakerPolicy = Policy
.Handle<TimeoutException>()
.CircuitBreakerAsync(3, TimeSpan.FromSeconds(2));
var retryPolicy = Policy.Handle<TimeoutException>().RetryAsync(3);
// Combined policy: outermost first, innermost last
var policy = Policy.WrapAsync(retryPolicy, circuitBreakerPolicy);
Использование политики
await this.policy.ExecuteAsync(async () => await SomeFooMethodAsync(cancellationToken));