АрхитектураАрхитектура ПОАрхитектор распределённых систем

Распределённый сервис вызывает нестабильную внешнюю систему, из за чего растут тайм ауты и исчерпываются ра...

Распределённый сервис вызывает нестабильную внешнюю систему, из-за чего растут тайм-ауты и исчерпываются рабочие потоки. Какой механизм должен разорвать каскад отказов?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Для разрыва каскада отказов применяют предохранитель вызовов (circuit breaker). Он временно прекращает обращения к неисправной зависимости после порога ошибок, быстро возвращает контролируемый результат и через паузу проверяет, восстановилась ли зависимость.

Исторический контекст

Подход появился как перенос идеи электрического автоматического выключателя в распределённые системы. Исходная проблема заключалась в том, что отказ одной зависимости распространялся на вызывающие компоненты через ожидание, повторные попытки и исчерпание ресурсов.

В отличие от простого тайм-аута, circuit breaker сохраняет наблюдаемое состояние зависимости между вызовами. Это позволяет не отправлять заведомо обречённые запросы в течение периода её недоступности.

Постановка проблемы

Если внешний сервис отвечает медленно или не отвечает вовсе, каждый вызов занимает поток, соединение или другой ограниченный ресурс до истечения тайм-аута. При росте числа запросов вызывающий сервис сам становится недоступным, даже если его собственная бизнес-логика исправна.

Бесконтрольные повторы усугубляют ситуацию: они увеличивают нагрузку на уже неисправную зависимость. Простое увеличение тайм-аута обычно только дольше удерживает ресурсы, а полное отключение вызовов лишает систему возможности автоматически восстановить функциональность.

Подробное решение

Circuit breaker обычно имеет три состояния:

  • Закрыт — вызовы проходят, а ошибки и задержки учитываются в статистике.
  • Открыт — вызовы к зависимости не выполняются и сразу завершаются отказом или резервным сценарием.
  • Полуоткрыт — после заданной паузы пропускается ограниченное число проверочных вызовов. Успех возвращает предохранитель в закрытое состояние, повторные ошибки снова открывают его.

Переход в открытое состояние должен учитывать не только абсолютное число ошибок, но и окно наблюдения, долю неуспешных вызовов, тайм-ауты и иногда задержку. Порог выбирают так, чтобы кратковременный единичный сбой не отключал исправную зависимость, а устойчивый отказ не создавал лавину запросов.

Предохранитель не устраняет первопричину и не гарантирует успешный пользовательский сценарий. Он лишь ограничивает ущерб: вызывающий компонент должен иметь корректный fallback, например показать устаревшие данные, вернуть понятный временный отказ или передать операцию на последующую обработку.

Повторные попытки, тайм-ауты и circuit breaker решают разные задачи. Тайм-аут ограничивает длительность отдельного вызова, retry может восстановить кратковременный сбой, а circuit breaker предотвращает массовое выполнение вызовов при устойчивой неисправности. Повторы необходимо ограничивать и сочетать с задержкой и случайным разбросом, иначе они сами создадут перегрузку.

Нужны метрики переходов состояний, доли отказов, времени восстановления и количества подавленных вызовов. Без наблюдаемости открытый предохранитель легко принять за причину проблемы, хотя он часто только делает уже существующий отказ явным.

Ситуация из практики

Сервис оформления заказа обращался к системе рекомендаций синхронно. При деградации рекомендаций её ответы занимали до нескольких секунд, из-за чего рабочие потоки сервиса заказов блокировались и задерживались даже операции, не связанные с рекомендациями.

Рассматривались три варианта. Увеличение тайм-аута ухудшало удержание ресурсов. Полное удаление рекомендаций повышало доступность, но лишало продукт функции даже при кратковременных сбоях. Неограниченные повторы могли временно скрыть сетевые ошибки, однако увеличивали нагрузку и задержку.

Выбрали короткий тайм-аут, ограниченные повторы только для безопасных запросов и circuit breaker с резервом на основе последнего успешно сохранённого результата. После серии ошибок новые запросы к рекомендациям быстро пропускались мимо, а проверка восстановления выполнялась ограниченно.

В результате зависимость перестала блокировать оформление заказа, но команда отдельно определила срок допустимой давности резервных данных и правила полного отказа, когда устаревший результат уже неприемлем. Это важный компромисс: повышение доступности достигалось ценой возможной неполноты или устаревания второстепенной функции.

Что кандидаты часто упускают

1. Достаточно ли circuit breaker без тайм-аутов?

Нет. Предохранитель может открыть доступ к зависимости только после фиксации неудачи, а отдельный вызов до этого всё равно способен зависнуть надолго. Тайм-аут ограничивает продолжительность такого вызова; circuit breaker ограничивает последующие попытки при накоплении признаков устойчивого отказа.

2. Нужно ли открывать предохранитель на любую ошибку?

Нет. Следует различать ошибки зависимости, сетевые тайм-ауты и временную перегрузку с одной стороны, а ошибки валидации или бизнес-отказы — с другой. Если считать все ответы ошибками зависимости, предохранитель будет отключать исправную систему из-за некорректных входных данных клиентов.

3. Почему fallback может быть опаснее быстрого отказа?

Fallback способен вернуть устаревшие или неполные данные, которые пользователь воспримет как актуальные. Для денежных операций, прав доступа и других критичных решений безопаснее явно сообщить о временной недоступности, чем молча использовать значение с неизвестной корректностью.