Разберите, как отказ одного сервиса превращается в каскадную недоступность при синхронной цепочке вызовов.
При синхронной цепочке каждый вызывающий сервис удерживает запрос, пока не получит ответ от зависимости. Если зависимость замедляется или недоступна, растёт число занятых потоков, соединений и других ресурсов; это истощение распространяется по цепочке и делает недоступными сервисы, которые сами по себе исправны.
Основная защита — ограничивать время и объём ожидания: задавать тайм-ауты, применять ограничение параллелизма, изолировать пулы ресурсов и временно прекращать вызовы отказавшей зависимости с помощью circuit breaker. Эти механизмы уменьшают радиус отказа, но не делают операцию автоматически успешной.
Синхронная интеграция появилась как естественный способ получить ответ от другой части системы в рамках одной пользовательской операции. Она упрощает модель программирования: вызывающая сторона сразу получает результат или ошибку и может принять решение.
По мере разделения системы на независимые сервисы такая простота стала сочетаться с сетевыми задержками, частичными отказами и ограниченными ресурсами. Поэтому для распределённых систем понадобились явные правила управления ожиданием и изоляции отказов.
Предположим, внешний запрос проходит через сервис заказов, сервис расчёта цены и сервис доставки. Если сервис доставки начинает отвечать за несколько секунд вместо сотен миллисекунд, запросы к нему накапливаются, а сервис расчёта цены продолжает удерживать свои ресурсы в ожидании.
Когда число таких запросов достигает лимита пула потоков или соединений, сервис расчёта цены начинает задерживать уже собственные запросы. Затем аналогичный эффект возникает в сервисе заказов. В результате локальная проблема одной зависимости превращается в рост задержек, тайм-аутов, повторных попыток и общей недоступности.
Особенно опасны не только полные отказы, но и частичные деградации. Повторные попытки без общего бюджета времени могут многократно увеличить нагрузку на уже перегруженную зависимость.
Каждый синхронный вызов должен иметь ограничение времени ожидания, согласованное с бюджетом задержки всей пользовательской операции. Тайм-аут внутреннего вызова не должен бессистемно превышать оставшееся время обработки внешнего запроса; иначе зависимость продолжит работать после того, как результат уже не имеет практической ценности.
Circuit breaker отслеживает ошибки или превышение задержек. После достижения порога он размыкается и временно отклоняет новые вызовы без обращения к неисправной зависимости. Через ограниченное время выполняются пробные вызовы; при восстановлении зависимость снова становится доступной, при неудаче блокировка продлевается.
Одного circuit breaker недостаточно. Ограничение параллелизма не позволяет одной зависимости занять все рабочие ресурсы, а bulkhead-изоляция разделяет пулы потоков, соединений или квоты для разных зависимостей. Поэтому перегрузка одного направления не обязана блокировать остальные.
Повторные попытки допустимы только для операций, где повтор безопасен или обеспечена идемпотентность. Их число должно быть ограничено, а задержка между попытками — контролироваться. Иначе каждый уровень цепочки может умножать нагрузку: один внешний запрос порождает несколько внутренних обращений на каждом уровне.
Если бизнес-операция допускает неполный результат, применяют запасное поведение: кэшированные данные, заранее определённое значение по умолчанию или перевод операции в асинхронный процесс. Такое решение требует явного согласования с бизнесом, потому что отказ от актуального ответа меняет семантику операции.
Событийная интеграция может разорвать синхронную цепочку, если вызывающей стороне не нужен немедленный результат. Но она переносит проблему в другую плоскость: появляются задержка доставки, повторная обработка, необходимость идемпотентных обработчиков и контроль состояния обработки. Это не универсальная замена синхронным вызовам.
В системе оформления заказа сервис заказов синхронно запрашивал сервис рекомендаций перед подтверждением страницы. Сервис рекомендаций начал отвечать с большой задержкой из-за перегрузки хранилища. Сначала выросло время ответа страницы, затем пул соединений сервиса заказов оказался занят ожиданием, и обычные операции заказа стали получать тайм-ауты.
Рассматривались три варианта. Увеличение тайм-аутов временно снижало число ошибок верхнего уровня, но усиливало удержание ресурсов. Полный перевод рекомендаций в обязательную асинхронную цепочку требовал изменения пользовательского сценария. Простое отключение вызова устраняло нагрузку, но не давало контролируемого поведения при частичных сбоях.
Выбрали короткий тайм-аут, отдельный пул соединений, ограничение параллельных запросов и circuit breaker. При недоступности рекомендаций заказ продолжал оформляться без этого необязательного блока, а пользователю показывался результат без персональных рекомендаций. В итоге отказ второстепенной зависимости перестал блокировать основной сценарий; при этом команда отдельно настроила метрики открытий circuit breaker, задержек и доли ответов без рекомендаций.
Ответ: Внешний тайм-аут завершит клиентский запрос, но внутренние вызовы могут продолжать занимать потоки, соединения и память. Если такие фоновые ожидания не отменяются или не ограничиваются на уровне зависимостей, система будет накапливать бесполезную работу. Тайм-ауты должны образовывать согласованную цепочку, а отмена обработки должна учитываться каждым участником, который может продолжать расходовать ресурсы.
Ответ: Зависимость может возвращать успешные ответы, но настолько медленно, что вызывающая сторона всё равно исчерпывает свои ресурсы. Если учитывать только явные ошибки, breaker не заметит деградацию по задержке. Поэтому критерии должны учитывать подходящие для конкретного вызова признаки: превышение задержки, долю ошибок, тайм-ауты и иногда отказ по ограничению параллелизма. Порог и окно наблюдения нельзя выбирать независимо от нормальной нагрузки и допустимого бюджета задержки.
Ответ: Повторы опасны, когда операция неидемпотентна, а у клиента нет способа отличить неизвестный результат от гарантированного отказа. Например, запрос мог быть принят зависимостью, но ответ потерялся; повтор способен создать второе бизнес-действие. В такой ситуации сначала нужен механизм идемпотентности или проверка результата, а до его появления отказ от автоматических повторов может быть безопаснее. Даже для идемпотентных операций повторы должны учитывать общий бюджет времени и нагрузку на зависимость.