Распределённый сервис вызывает нестабильную внешнюю систему, из-за чего растут тайм-ауты и исчерпываются рабочие потоки. Какой механизм должен разорвать каскад отказов?
Для разрыва каскада отказов применяют предохранитель вызовов (circuit breaker). Он временно прекращает обращения к неисправной зависимости после порога ошибок, быстро возвращает контролируемый результат и через паузу проверяет, восстановилась ли зависимость.
Подход появился как перенос идеи электрического автоматического выключателя в распределённые системы. Исходная проблема заключалась в том, что отказ одной зависимости распространялся на вызывающие компоненты через ожидание, повторные попытки и исчерпание ресурсов.
В отличие от простого тайм-аута, circuit breaker сохраняет наблюдаемое состояние зависимости между вызовами. Это позволяет не отправлять заведомо обречённые запросы в течение периода её недоступности.
Если внешний сервис отвечает медленно или не отвечает вовсе, каждый вызов занимает поток, соединение или другой ограниченный ресурс до истечения тайм-аута. При росте числа запросов вызывающий сервис сам становится недоступным, даже если его собственная бизнес-логика исправна.
Бесконтрольные повторы усугубляют ситуацию: они увеличивают нагрузку на уже неисправную зависимость. Простое увеличение тайм-аута обычно только дольше удерживает ресурсы, а полное отключение вызовов лишает систему возможности автоматически восстановить функциональность.
Circuit breaker обычно имеет три состояния:
Переход в открытое состояние должен учитывать не только абсолютное число ошибок, но и окно наблюдения, долю неуспешных вызовов, тайм-ауты и иногда задержку. Порог выбирают так, чтобы кратковременный единичный сбой не отключал исправную зависимость, а устойчивый отказ не создавал лавину запросов.
Предохранитель не устраняет первопричину и не гарантирует успешный пользовательский сценарий. Он лишь ограничивает ущерб: вызывающий компонент должен иметь корректный fallback, например показать устаревшие данные, вернуть понятный временный отказ или передать операцию на последующую обработку.
Повторные попытки, тайм-ауты и circuit breaker решают разные задачи. Тайм-аут ограничивает длительность отдельного вызова, retry может восстановить кратковременный сбой, а circuit breaker предотвращает массовое выполнение вызовов при устойчивой неисправности. Повторы необходимо ограничивать и сочетать с задержкой и случайным разбросом, иначе они сами создадут перегрузку.
Нужны метрики переходов состояний, доли отказов, времени восстановления и количества подавленных вызовов. Без наблюдаемости открытый предохранитель легко принять за причину проблемы, хотя он часто только делает уже существующий отказ явным.
Сервис оформления заказа обращался к системе рекомендаций синхронно. При деградации рекомендаций её ответы занимали до нескольких секунд, из-за чего рабочие потоки сервиса заказов блокировались и задерживались даже операции, не связанные с рекомендациями.
Рассматривались три варианта. Увеличение тайм-аута ухудшало удержание ресурсов. Полное удаление рекомендаций повышало доступность, но лишало продукт функции даже при кратковременных сбоях. Неограниченные повторы могли временно скрыть сетевые ошибки, однако увеличивали нагрузку и задержку.
Выбрали короткий тайм-аут, ограниченные повторы только для безопасных запросов и circuit breaker с резервом на основе последнего успешно сохранённого результата. После серии ошибок новые запросы к рекомендациям быстро пропускались мимо, а проверка восстановления выполнялась ограниченно.
В результате зависимость перестала блокировать оформление заказа, но команда отдельно определила срок допустимой давности резервных данных и правила полного отказа, когда устаревший результат уже неприемлем. Это важный компромисс: повышение доступности достигалось ценой возможной неполноты или устаревания второстепенной функции.
1. Достаточно ли circuit breaker без тайм-аутов?
Нет. Предохранитель может открыть доступ к зависимости только после фиксации неудачи, а отдельный вызов до этого всё равно способен зависнуть надолго. Тайм-аут ограничивает продолжительность такого вызова; circuit breaker ограничивает последующие попытки при накоплении признаков устойчивого отказа.
2. Нужно ли открывать предохранитель на любую ошибку?
Нет. Следует различать ошибки зависимости, сетевые тайм-ауты и временную перегрузку с одной стороны, а ошибки валидации или бизнес-отказы — с другой. Если считать все ответы ошибками зависимости, предохранитель будет отключать исправную систему из-за некорректных входных данных клиентов.
3. Почему fallback может быть опаснее быстрого отказа?
Fallback способен вернуть устаревшие или неполные данные, которые пользователь воспримет как актуальные. Для денежных операций, прав доступа и других критичных решений безопаснее явно сообщить о временной недоступности, чем молча использовать значение с неизвестной корректностью.