Что будешь делать если после запроса REST соединение сломается?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Общий подход к обработке сбоя соединения в REST API
При сбое сетевого соединения после отправки REST запроса ключевая задача — определить, был ли запрос обработан сервером или нет. Это идемпотентность и идемпотентные операции становятся центральными концепциями. Я разделю стратегии на клиентскую и серверную стороны.
1. Клиентская сторона (Наша ответственность)
Основная тактика — повторные попытки (retry logic) с учетом идемпотентности операции.
Идемпотентные операции (GET, PUT, DELETE, PATCH*)
Для безопасных повторений используем экспоненциальную отсрочку (exponential backoff) и джиттер (jitter):
public class RetryHandler : DelegatingHandler
{
private readonly int _maxRetries = 3;
private readonly Random _jitter = new();
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
HttpResponseMessage? response = null;
for (int attempt = 1; attempt <= _maxRetries; attempt++)
{
try
{
response = await base.SendAsync(request, cancellationToken);
if (response.IsSuccessStatusCode)
return response;
// Повторяем только при сетевых ошибках или 5xx
if ((int)response.StatusCode < 500)
break;
}
catch (HttpRequestException ex)
{
// Логируем исключение
_logger.LogWarning(ex, "Attempt {Attempt} failed", attempt);
}
if (attempt < _maxRetries)
{
var delay = TimeSpan.FromSeconds(Math.Pow(2, attempt))
+ TimeSpan.FromMilliseconds(_jitter.Next(0, 1000));
await Task.Delay(delay, cancellationToken);
}
}
return response ?? throw new HttpRequestException("All retry attempts failed");
}
}
Неидемпотентные операции (POST)
Для POST запросов, создающих ресурсы, стратегия сложнее:
- Проверка по уникальному идентификатору — включаем
Idempotency-Keyв заголовки - Сначала локальная валидация, затем отправка
- Компенсирующие транзакции (compensating transactions) для отката
2. Серверная сторона (Что должно быть реализовано)
Идемпотентность на сервере
[ApiController]
public class OrdersController : ControllerBase
{
[HttpPost("orders")]
public async Task<IActionResult> CreateOrder(
[FromHeader(Name = "Idempotency-Key")] string idempotencyKey,
[FromBody] OrderRequest request)
{
// Проверяем, не обрабатывался ли этот ключ ранее
var existingOrder = await _idempotencyService
.GetByKeyAsync(idempotencyKey);
if (existingOrder != null)
return Ok(existingOrder); // Возвращаем существующий результат
// Обработка запроса и сохранение ключа идемпотентности
var order = await _orderService.CreateAsync(request);
await _idempotencyService.StoreAsync(idempotencyKey, order);
return CreatedAtAction(nameof(GetOrder), new { id = order.Id }, order);
}
}
3. Паттерны проектирования для надежности
Circuit Breaker (Автоматический выключатель)
// Используем Polly для реализации Circuit Breaker
var circuitBreakerPolicy = Policy<HttpResponseMessage>
.Handle<HttpRequestException>()
.OrResult(r => (int)r.StatusCode >= 500)
.CircuitBreakerAsync(
exceptionsAllowedBeforeBreaking: 3,
durationOfBreak: TimeSpan.FromSeconds(30)
);
Outbox Pattern для гарантированной доставки
Для критических операций, где потеря запроса недопустима:
- Сохраняем запрос в локальную БД (таблицу Outbox)
- Фоновая задача пытается отправить запросы из Outbox
- Помечаем запрос как отправленный после подтверждения
Компенсационные транзакции (Saga Pattern)
Для распределенных транзакций:
public class OrderSaga
{
public async Task ProcessOrder(Order order)
{
try
{
await _inventoryService.ReserveItems(order.Items);
await _paymentService.Charge(order.Total);
await _shippingService.ScheduleDelivery(order);
}
catch (Exception)
{
// Компенсация всех выполненных шагов
await Compensate(order);
throw;
}
}
}
4. Мониторинг и анализ
- Логирование всех попыток и сбоев с корреляционными идентификаторами
- Метрики: количество сбоев, время восстановления, успешность retry
- Alerting при превышении порога ошибок
- Трассировка распределенных транзакций через OpenTelemetry
5. Практические рекомендации
Настройки HttpClient для production:
services.AddHttpClient("ResilientClient")
.ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler
{
PooledConnectionLifetime = TimeSpan.FromMinutes(5),
ConnectTimeout = TimeSpan.FromSeconds(30),
ResponseDrainTimeout = TimeSpan.FromSeconds(15)
})
.AddPolicyHandler(GetRetryPolicy())
.AddPolicyHandler(GetCircuitBreakerPolicy());
Ключевые параметры:
- Таймауты: Connect, Send, Receive
- Максимальное количество соединений
- Lifetime пула соединений
Для пользовательского интерфейса:
- Показывать индикатор выполнения
- Предлагать повторить действие вручную
- Сохранять состояние формы при transient failures
Заключение
Сбои соединений — не исключение, а норма в распределенных системах. Resilience engineering предполагает проектирование системы с учетом отказов. Современный подход — использовать комбинацию:
- Повторных попыток с интеллектуальными задержками
- Идемпотентности на уровне бизнес-логики
- Шаблонов устойчивости (Circuit Breaker, Retry, Timeout)
- Распределенных транзакций с компенсацией
- Исчерпывающего мониторинга
В .NET экосистеме библиотеки Polly, Coravel, MassTransit предоставляют готовые реализации этих паттернов. Важнее всего — определить SLA системы и выбрать стратегию, соответствующую бизнес-требованиям.