Что такое Polly и зачем она нужна?
Polly — это мощная и гибкая библиотека для .NET (C#, F#, VB.NET), предназначенная для обработки временных сбоев и повышения отказоустойчивости приложений. Она реализует паттерны устойчивости, такие как повторные попытки (Retry), размыкание цепи (Circuit Breaker), тайм-ауты (Timeout), изоляция (Bulkhead Isolation) и кэширование (Cache).
В современном мире микросервисной архитектуры и облачных вычислений приложения постоянно взаимодействуют с внешними ресурсами: базами данных, веб-сервисами, API. Эти взаимодействия могут быть ненадёжными — сетевые ошибки, временная недоступность сервиса, превышение лимитов запросов. Polly позволяет обрабатывать такие ситуации элегантно, без написания громоздкого кода с try-catch блоками.
Основные сценарии использования:
- Retry (Повторные попытки): Автоматически повторять операцию при временной ошибке.
- Circuit Breaker (Размыкание цепи): Прекращать попытки вызова, если сервис постоянно выдаёт ошибки, чтобы дать ему время восстановиться.
- Timeout (Тайм-аут): Ограничивать время выполнения операции.
- Bulkhead (Изоляция): Ограничивать количество параллельных операций, чтобы одна "плохая" операция не заблокировала весь пул потоков.
- Cache (Кэширование): Кэшировать результаты успешных операций.
- Fallback (Запасной вариант): Выполнять альтернативное действие в случае сбоя.
Установка Polly
Polly распространяется через NuGet — менеджер пакетов для .NET. Установить её можно несколькими способами:
Через Package Manager Console (Visual Studio)
Install-Package Polly
Через .NET CLI
dotnet add package Polly
Через PackageReference (в .csproj файле)
<PackageReference Include="Polly" Version="8.2.0" />
После установки добавьте using-директиву в ваш код:
using Polly;
using Polly.Retry;
using Polly.CircuitBreaker;
using Polly.Timeout;
using Polly.Bulkhead;
using Polly.Caching;
using Polly.Fallback;
using Polly.Wrap;
Быстрый старт с примером кода
Рассмотрим простейший пример: у нас есть метод, который вызывает внешний API, и мы хотим повторить вызов 3 раза, если возникает исключение.
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Polly;
using Polly.Retry;
public class ApiClient
{
private readonly HttpClient _httpClient;
private readonly AsyncRetryPolicy _retryPolicy;
public ApiClient()
{
_httpClient = new HttpClient();
// Создаём политику повторных попыток
_retryPolicy = Policy
.Handle<HttpRequestException>() // Какие исключения обрабатывать
.Or<TimeoutException>() // Или тайм-аут
.WaitAndRetryAsync(
3, // Количество попыток
retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), // Экспоненциальная задержка
onRetry: (exception, timeSpan, retryCount, context) =>
{
Console.WriteLine($"Попытка {retryCount} не удалась. Повтор через {timeSpan.Seconds} сек. Ошибка: {exception.Message}");
}
);
}
public async Task<string> GetDataAsync(string url)
{
// Выполняем запрос с использованием политики
return await _retryPolicy.ExecuteAsync(async () =>
{
var response = await _httpClient.GetAsync(url);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
});
}
}
// Использование
public class Program
{
public static async Task Main()
{
var client = new ApiClient();
try
{
var data = await client.GetDataAsync("https://api.example.com/data");
Console.WriteLine($"Получены данные: {data}");
}
catch (Exception ex)
{
Console.WriteLine($"Все попытки исчерпаны. Ошибка: {ex.Message}");
}
}
}
Основные методы и функции Polly
Polly предоставляет богатый набор методов для создания политик устойчивости. Рассмотрим 10 ключевых методов с примерами.
1. Handle<TException>()
Что делает: Определяет, какие типы исключений должна обрабатывать политика. Это базовый метод для всех политик.
Параметры: Generic-параметр TException — тип исключения.
// Обрабатываем только HttpRequestException
var policy = Policy.Handle<HttpRequestException>();
// Обрабатываем несколько типов исключений
var policy = Policy
.Handle<HttpRequestException>()
.Or<TimeoutException>()
.Or<TaskCanceledException>();
2. HandleResult<TResult>()
Что делает: Определяет, какие результаты (не исключения) должна обрабатывать политика. Полезно, когда API возвращает код ошибки, а не выбрасывает исключение.
Параметры: Func<TResult, bool> — функция-предикат, которая проверяет результат.
// Обрабатываем результат, если он равен null или содержит ошибку
var policy = Policy
.HandleResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
.RetryAsync(3);
// Использование
var result = await policy.ExecuteAsync(() => httpClient.GetAsync("https://api.example.com"));
3. Retry() / RetryAsync()
Что делает: Создаёт политику повторных попыток. При возникновении исключения (или неудовлетворительного результата) операция будет повторена указанное количество раз.
Параметры: retryCount (int) — количество попыток; onRetry (Action) — действие, выполняемое при каждой попытке.
// Простая политика: 3 попытки без задержки
var retryPolicy = Policy
.Handle<HttpRequestException>()
.RetryAsync(3);
// С действием при каждой попытке
var retryPolicyWithAction = Policy
.Handle<HttpRequestException>()
.RetryAsync(3, onRetry: (exception, retryCount) =>
{
Console.WriteLine($"Попытка {retryCount} не удалась: {exception.Message}");
});
// Синхронная версия
var syncRetryPolicy = Policy
.Handle<Exception>()
.Retry(3);
4. WaitAndRetry() / WaitAndRetryAsync()
Что делает: Создаёт политику повторных попыток с задержкой между ними. Задержка может быть фиксированной, экспоненциальной или вычисляемой динамически.
Параметры: retryCount (int); sleepDurationProvider (Func<int, TimeSpan>) — функция, определяющая задержку; onRetry (Action).
// Фиксированная задержка: 2 секунды между попытками
var fixedDelayPolicy = Policy
.Handle<HttpRequestException>()
.WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(2));
// Экспоненциальная задержка: 2^1=2с, 2^2=4с, 2^3=8с
var exponentialDelayPolicy = Policy
.Handle<HttpRequestException>()
.WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
// Динамическая задержка с джиттером (случайное отклонение)
var jitterPolicy = Policy
.Handle<HttpRequestException>()
.WaitAndRetryAsync(3, retryAttempt =>
{
var baseDelay = TimeSpan.FromSeconds(Math.Pow(2, retryAttempt));
var jitter = TimeSpan.FromMilliseconds(new Random().Next(0, 1000));
return baseDelay + jitter;
});
5. CircuitBreaker() / CircuitBreakerAsync()
Что делает: Создаёт политику размыкания цепи. Если количество ошибок превышает порог за определённое время, цепь "размыкается" и последующие вызовы сразу завершаются ошибкой, давая сервису время восстановиться.
Параметры: exceptionsAllowedBeforeBreaking (int) — количество ошибок для размыкания; durationOfBreak (TimeSpan) — время, на которое цепь размыкается; onBreak, onReset, onHalfOpen (Action) — колбэки.
// Политика размыкания цепи: 3 ошибки за 30 секунд, размыкание на 1 минуту
var circuitBreakerPolicy = Policy
.Handle<HttpRequestException>()
.CircuitBreakerAsync(
3, // Количество ошибок для размыкания
TimeSpan.FromMinutes(1), // Время размыкания
onBreak: (exception, duration) =>
{
Console.WriteLine($"Цепь разомкнута на {duration.TotalSeconds} сек. Ошибка: {exception.Message}");
},
onReset: () =>
{
Console.WriteLine("Цепь восстановлена.");
},
onHalfOpen: () =>
{
Console.WriteLine("Цепь в полуоткрытом состоянии. Тестируем...");
}
);
// Использование
try
{
var result = await circuitBreakerPolicy.ExecuteAsync(() => SomeUnreliableMethod());
}
catch (BrokenCircuitException ex)
{
Console.WriteLine("Цепь разомкнута. Вызов не выполнен.");
}
catch (HttpRequestException ex)
{
Console.WriteLine($"Ошибка HTTP: {ex.Message}");
}
6. Timeout() / TimeoutAsync()
Что делает: Создаёт политику тайм-аута. Операция будет прервана, если она выполняется дольше указанного времени.
Параметры: seconds (int) или timeout (TimeSpan); timeoutStrategy (TimeoutStrategy) — Optimistic (оптимистичный) или Pessimistic (пессимистичный).
// Простой тайм-аут: 5 секунд
var timeoutPolicy = Policy
.TimeoutAsync(5);
// Тайм-аут с TimeSpan
var timeoutPolicyWithSpan = Policy
.TimeoutAsync(TimeSpan.FromSeconds(5));
// Пессимистичный тайм-аут (прерывает поток)
var pessimisticTimeout = Policy
.TimeoutAsync(5, TimeoutStrategy.Pessimistic);
// Использование с обработкой исключения
try
{
var result = await timeoutPolicy.ExecuteAsync(async () =>
{
await Task.Delay(10000); // Долгая операция
return "Done";
});
}
catch (TimeoutRejectedException ex)
{
Console.WriteLine("Операция превысила тайм-аут.");
}
7. Bulkhead() / BulkheadAsync()
Что делает: Создаёт политику изоляции (Bulkhead). Ограничивает максимальное количество параллельных операций и количество ожидающих операций в очереди.
Параметры: maxParallelization (int) — максимальное количество параллельных операций; maxQueuingActions (int) — максимальный размер очереди; onBulkheadRejected (Action) — действие при отклонении.
// Изоляция: не более 2 параллельных операций, очередь не более 4
var bulkheadPolicy = Policy
.BulkheadAsync(
2, // Максимум параллельных операций
4, // Максимум в очереди
onBulkheadRejected: context =>
{
Console.WriteLine("Операция отклонена из-за переполнения очереди.");
}
);
// Использование
var tasks = new List<Task>();
for (int i = 0; i < 10; i++)
{
tasks.Add(bulkheadPolicy.ExecuteAsync(async () =>
{
Console.WriteLine($"Начало операции {i}");
await Task.Delay(1000);
Console.WriteLine($"Завершение операции {i}");
}));
}
await Task.WhenAll(tasks);
8. Fallback() / FallbackAsync()
Что делает: Создаёт политику запасного варианта. Если основная операция завершается ошибкой, выполняется альтернативное действие.
Параметры: fallbackAction (Func<CancellationToken, Task<TResult>>) — действие, выполняемое при сбое; onFallback (Action) — действие при активации запасного варианта.
// Запасной вариант: возвраща Также в библиотеке
ORM для C++: сравнение и примеры использования (ODB, SOCI, SQLiteCpp)
Работа с Excel в PHP: библиотеки PhpSpreadsheet и OpenSpout
Отправка email на Rust: библиотеки lettre и mailparse для писем
Email отправка на JavaScript (Node.js) — библиотеки и примеры кода