Получить доступ

Resilience: таймауты, ретраи и circuit breaker

Защита от каскадных отказов: таймауты, безопасные ретраи, состояния circuit breaker, bulkhead и rate limiter, fallback и порядок паттернов.

Зачем resilience

В распределённой системе внешние вызовы неизбежно падают и тормозят. Без защиты медленная или упавшая зависимость исчерпывает потоки/соединения вызывающего сервиса, и сбой каскадно валит всю систему (cascading failure).

Цель resilience-паттернов — локализовать сбой: быстро отказать, не дёргать упавшую зависимость и деградировать управляемо. В Spring стандарт — библиотека Resilience4j (лёгкая, функциональная; пришла на смену устаревшему Hystrix), интегрируется аннотациями и метриками.

Таймауты — основа

Самое важное правило: у каждого внешнего вызова должен быть таймаут (на установление соединения и на чтение ответа). Без таймаута поток/соединение/event loop виснет до исчерпания ресурса — и это первый шаг к каскаду.

Таймаут важнее ретраев: бессмысленно повторять то, что может висеть вечно. В Resilience4j за ограничение времени отвечает TimeLimiter (особенно для асинхронных вызовов).

Безопасные ретраи

Ретрай помогает ТОЛЬКО при временной (transient) ошибке и только для идемпотентных операций — повтор неидемпотентного POST создаёт дубликаты. У ретраев обязателен лимит попыток.

Между попытками — экспоненциальный backoff с jitter (случайной добавкой): без них тысячи клиентов синхронно повторят запрос в один момент и добьют уже шатающуюся зависимость (retry storm / thundering herd). Ретрай без таймаута и идемпотентности обычно усиливает инцидент, а не лечит его.

Circuit breaker

Circuit breaker (предохранитель) отслеживает долю ошибок к зависимости и имеет три состояния. CLOSED — вызовы проходят, считается частота сбоев. При превышении порога он переходит в OPEN — на время вызовы НЕ идут вовсе, а сразу падают (fail fast), давая зависимости восстановиться и не тратя на неё потоки. Через паузу — HALF-OPEN: пропускается несколько пробных вызовов; успех → CLOSED, провал → снова OPEN.

Это ключевая защита от каскада: вместо того чтобы 10 000 запросов висели на таймаутах упавшего сервиса, предохранитель мгновенно отклоняет их.

@CircuitBreaker(name = "payments", fallbackMethod = "fallback")
@Retry(name = "payments")
Payment charge(Order o) { return paymentsClient.charge(o); }

Payment fallback(Order o, Throwable t) { return Payment.pending(); } // деградация
Автомат circuit breaker с тремя состояниями: Закрыт (запросы проходят), Открыт (запросы сразу отклоняются), Полуоткрыт (пробный запрос); переходы по порогу ошибок, таймауту, успеху и ошибке.
Circuit breaker: Закрыт → Открыт при превышении порога ошибок, через таймаут → Полуоткрыт, далее успех возвращает в Закрыт, ошибка — снова в Открыт.

Bulkhead и rate limiter

Bulkhead («переборка», как отсеки корабля) изолирует ресурсы под каждую зависимость — отдельный пул потоков или семафор с лимитом одновременных вызовов. Тогда зависшая зависимость съедает только свой отсек, а не все потоки сервиса.

Rate limiter ограничивает частоту вызовов (входящих или исходящих) — не берёт работы больше, чем система/зависимость способна переварить, обеспечивая backpressure и соблюдение чужих лимитов.

Fallback и комбинирование

Когда паттерн срабатывает (open breaker, исчерпаны ретраи, таймаут), нужен fallback — управляемая деградация: вернуть закэшированное/дефолтное/частичное значение вместо ошибки, если домен это допускает. Молчаливый fallback опасен — его обязательно логируют и мониторят.

Паттерны комбинируют, и порядок важен. По умолчанию в Resilience4j аспекты вложены так: Retry ( CircuitBreaker ( RateLimiter ( TimeLimiter ( Bulkhead ( вызов ) ) ) ) ). Retry — самый внешний: каждая попытка проходит через предохранитель и учитывается в его статистике, а когда предохранитель разомкнут, повторы сразу получают отказ и не нагружают зависимость. Fallback срабатывает последним, когда исчерпано всё остальное. Все эти паттерны экспортируют метрики в Micrometer и Actuator — мониторинг устойчивости разбирается в уроке про Actuator.

Как отвечать на собеседовании

Начните с проблемы каскадного отказа и тезиса «таймаут важнее ретрая». Объясните безопасные ретраи (transient + идемпотентность + backoff/jitter против retry storm) и три состояния circuit breaker (CLOSED/OPEN/HALF-OPEN) как защиту от каскада. Добавьте bulkhead (изоляция ресурсов) и rate limiter (backpressure), fallback как деградацию, и упомяните Resilience4j + порядок комбинирования и метрики.

Вопросы по теме

Читайте также

Закрытая бета

Знать тему — половина дела

На живом собеседовании sotto слышит вопрос интервьюера и показывает короткий ответ и план. Оставьте контакт — пришлём доступ и промокод.

  • Промокод на 2 часа — сверх пробных 30 минут
  • Программа для Windows — скачать сразу, войти — через Яндекс ID или VK ID
  • Прямая связь с разработчиком: что улучшить — сделаем