Circuit breaker: предохранитель для нестабильных зависимостей
Circuit breaker: предохранитель для нестабильных зависимостей
Circuit breaker (автоматический выключатель, или «предохранитель») временно прекращает вызовы к зависимому сервису после серии сбоев, чтобы дать ему восстановиться и не позволить проблеме распространиться на вызывающую систему.
История
Идея названа в честь электрического автоматического выключателя: при чрезмерном токе он размыкает цепь, а после устранения причины его можно включить снова. В программной инженерии паттерн стал особенно важен с распространением распределённых систем и удалённых вызовов, где отказ чужого процесса или сети — обычный сценарий, а не исключение.
Широко известное описание паттерна опубликовано Мартином Фаулером в 2014 году в статье «CircuitBreaker». Фаулер опирался на работу Майкла Найгарда о надёжных системах; второе издание его книги Release It! вышло в 2018 году. У паттерна нет единственного автора или нормативного RFC: это архитектурный приём, который библиотеки реализуют по-разному.
Важные вехи — появление библиотек отказоустойчивости для сервисных приложений, затем их перенос в облачные платформы и service mesh (сетевой слой управления взаимодействиями сервисов). Например, Netflix Hystrix популяризовал набор таких механизмов, включая circuit breaker, но проект переведён в режим обслуживания в 2018 году. В современных системах его идеи доступны в Resilience4j для Java, Polly для .NET, а также в прокси и mesh-платформах. Владельца у самого паттерна нет; конкретные реализации поддерживают соответствующие проекты.
Что это такое
Представь, что приложение вызывает платёжный сервис. Если тот замедлился, каждый запрос приложения может ждать ответа несколько секунд. При большом потоке такие ожидания занимают рабочие потоки и соединения. Очередь растёт, таймауты срабатывают, а уже перегруженное приложение тратит силы на повторные попытки. Circuit breaker следит за результатами вызовов и при заданном условии временно перестаёт отправлять новые запросы к проблемной зависимости.
У автомата обычно три состояния:
- Closed (закрыт) — нормальная работа: вызовы проходят, статистика ошибок собирается.
- Open (открыт) — вызовы быстро отклоняются или направляются в fallback (резервный ответ/путь), не нагружая зависимость.
- Half-open (полуоткрыт) — после паузы пропускается ограниченное число пробных запросов. Успех закрывает цепь; новые ошибки вновь открывают её.
Это не просто «считать ошибки». Нужно решить, какие ошибки учитывать, сколько их достаточно, за какой интервал измерять, сколько ждать до проверки и что возвращать вызывающему коду. Ответ «ошибка сервиса недоступна» часто правильнее фиктивного успешного результата.
Circuit breaker vs retry (повторная попытка). Retry полезен при кратковременном сбое одного вызова: например, сеть потеряла пакет. Но массовые немедленные повторы создают дополнительную нагрузку. Breaker останавливает поток вызовов, когда сбой стал систематическим. Их можно сочетать: немного повторов с задержкой внутри одного вызова, а breaker — как общий предохранитель.
Circuit breaker vs rate limiting (ограничение частоты). Rate limiter ограничивает скорость запросов независимо от того, работает ли получатель. Breaker реагирует на наблюдаемые сбои конкретной зависимости. Первое защищает от чрезмерного потока, второе — от продолжения бесполезной работы при деградации.
Аналогии из жизни
- Электрощиток. Автомат отключает линию, когда ток опасно велик; после устранения причины цепь можно включить снова. Это хорошо передаёт идею разрыва цепи ради защиты остального. Где ломается аналогия: программный breaker обычно переключается автоматически по статистике и не гарантирует, что открытие устранит саму причину сбоя.
- Диспетчерская на линии поддержки. Если конкретный отдел перегружен, оператор временно перестаёт переводить туда каждый звонок и предлагает оставить заявку. Это похоже на отказ вызова и fallback. Где ломается: программная зависимость может восстановиться сама, а «оставить заявку» не всегда эквивалентно исходной операции — особенно при оплате или изменении данных.
- Шлюз на мосту. При опасной нагрузке въезд закрывают, чтобы мост не перегружался дальше. Где ломается: в IT порог и режим проверки задаются метриками, а запросы могут иметь разную ценность; один порог не всегда подходит всем операциям.
Как это работает
Рассмотрим клиентский сервис, обращающийся к внешнему API.
- Пока breaker закрыт, клиент выполняет вызов и записывает результат вместе с длительностью. Обычно используется скользящее окно (учёт последних N вызовов или последних T секунд), чтобы старые ошибки постепенно переставали влиять на решение.
- Реализация классифицирует исход. Таймаут, отказ соединения и некоторые серверные ошибки обычно считаются сбоями. Ошибку валидации запроса или ожидаемый ответ «не найдено» зачастую не следует учитывать: это не означает, что зависимость сломалась.
- Когда выполнено минимальное число наблюдений и доля либо число ошибок превышают порог, состояние становится открытым. Например, настройка может требовать не менее 20 вызовов и более половины неудач в окне. Это лишь пример конфигурации, не универсальная рекомендация.
- В открытом состоянии новые обращения сразу получают контролируемый отказ. Они не ждут сетевого таймаута. Вызывающая сторона может показать понятное сообщение, прочитать локальный кэш или использовать ограниченный резервный сервис — если это корректно для данной операции.
- По истечении времени ожидания автомат переходит в полуоткрытое состояние. Он допускает небольшой набор пробных вызовов, часто один или несколько. Остальные по-прежнему блокируются, чтобы не устроить внезапный наплыв.
- Если пробы успешны, автомат закрывается и обычный поток возобновляется. Если снова обнаружены ошибки, он возвращается в открытое состояние, иногда с увеличенным временем ожидания.
Упрощённо:
Closed --ошибок достаточно--> Open
Open --пауза истекла--> Half-open
Half-open --пробы успешны--> Closed
Half-open --сбой--> Open
В распределённой системе важно, где живёт состояние. Если у каждого процесса свой breaker, тысячи экземпляров могут одновременно перейти в полуоткрытое состояние и устроить «стадный эффект» (одновременный всплеск запросов). Разброс времени проверки — jitter (случайное смещение) — помогает синхронизированные пробы разнести. Общий breaker, напротив, требует координации и может сам стать сложностью.
Операции записи требуют особой осторожности. После таймаута клиент не всегда знает, выполнилась ли операция на сервере. Повтор с fallback или при пробном вызове может привести к дубликату. Для этого нужны идемпотентные операции, ключи идемпотентности или отдельная проверка статуса, а не только breaker.
Где встречается в обычной жизни
- Приложение показывает данные из последнего успешного обновления, пока сервер временно не отвечает: это может быть fallback, защищённый breaker-ом.
- В онлайн-магазине каталог доступен, хотя сервис рекомендаций отключён: необязательная зависимость не должна остановить основную покупку.
- При звонке в службу поддержки система предлагает перезвонить, а не держит соединение бесконечно, если линия временно недоступна.
- В мобильном приложении ошибка одного удалённого компонента не обязана блокировать экран, который использует уже загруженные данные.
Где встречается в IT и бизнесе
- Микросервисы: не давать сбою сервиса профилей исчерпать потоки сервиса оформления заказов.
- Интеграции с внешними API: прекратить бесполезные вызовы после повторяющихся таймаутов или ответов 5xx.
- Базы данных и кэши: быстро переключить приложение на контролируемую деградацию при недоступности отдельного узла.
- Асинхронные задания: не отправлять каждую задачу немедленно в нестабильную систему; при этом очередь и retry-политика должны быть согласованы с breaker-ом.
- SaaS-продукты: сохранить критические сценарии, временно отключив второстепенные функции, зависящие от отдельного поставщика.
Для бизнеса ценность не в том, чтобы «не показывать ошибки», а в управляемом ухудшении сервиса: основной сценарий продолжает работать, а отказ зависимости виден команде.
Кто пользуется
Паттерн применим практически в любой системе, где есть сетевые зависимости, поэтому конкретные масштабы сильно различаются. Netflix публично описывал Hystrix как часть инфраструктуры отказоустойчивости; сегодня Hystrix не следует выбирать для нового внедрения только из-за исторической известности. В Java-проектах распространён Resilience4j, в .NET — Polly. Envoy и Istio позволяют настраивать похожие ограничения на уровне прокси и service mesh.
Публичных универсальных цифр вроде «все крупные компании используют breaker» нет. И число запросов в секунду само по себе не определяет необходимость: небольшой сервис с критичной зависимостью может нуждаться в нём сильнее, чем высоконагруженная система с хорошо изолированными компонентами.
Альтернативы и конкуренты
- Retry с экспоненциальной задержкой и jitter. Плюс: помогает пережить редкие временные сбои. Минус: не прекращает запросы при длительной неисправности и может усугубить перегрузку.
- Timeout (ограничение времени ожидания). Плюс: освобождает ресурсы, когда ответ задерживается. Минус: сам по себе не снижает число новых вызовов и не управляет восстановлением.
- Bulkhead (переборка/изоляция ресурсов). Плюс: лимитирует ресурсы, выделенные конкретной зависимости, и защищает соседние операции. Минус: не предотвращает бесполезные сетевые обращения и требует настройки пулов или лимитов.
- Fallback или деградация функции. Плюс: пользователь может продолжить основной сценарий. Минус: это стратегия ответа, а не механизм определения момента, когда зависимость следует временно отключить.
Обычно эти механизмы дополняют друг друга: timeout ограничивает ожидание, retry делает несколько разумных попыток, bulkhead изолирует ресурсы, breaker прекращает лавину обращений, а fallback определяет допустимое поведение для пользователя.
Когда НЕ стоит использовать
- Для каждого внутреннего вызова без анализа. Лишние автоматы затрудняют диагностику и могут скрыть ошибки конфигурации. Сначала нужны таймауты, наблюдаемость и понимание последствий отказа.
- Когда операция не может безопасно пережить отказ или резервный ответ. Нельзя выдумывать успешный результат для денежного перевода или сохранения заказа. Нужно честно сообщить о неопределённости и предусмотреть сверку.
- Вместо исправления постоянной неисправности. Breaker ограничивает последствия, но не чинит сервис, сеть, лимит квоты или неправильные учётные данные.
Связанные понятия
- Timeout: максимальное время ожидания ответа.
- Retry: повтор вызова после сбоя.
- Bulkhead: изоляция ресурсов, чтобы отказ одной части не затронул остальные.
- Fallback: резервное поведение при недоступности основного пути.
- Health check: проверка доступности или готовности компонента.
- Идемпотентность: свойство операции давать тот же итог при безопасном повторе.
Литература и источники
- Martin Fowler, «CircuitBreaker», 2014 — статья на martinfowler.com.
- Michael T. Nygard, Release It!: Design and Deploy Production-Ready Software, 2-е издание, 2018, английский.
- Официальная документация Resilience4j, раздел CircuitBreaker — искать «Resilience4j CircuitBreaker documentation».
- Официальная документация Polly — искать «Polly resilience strategies circuit breaker».
- Документация Envoy по circuit breaking — искать «Envoy circuit breaking» на envoyproxy.io.
- Документация Istio по outlier detection — искать «Istio outlier detection» на istio.io.
Где встретилось у меня
Понятие всплыло при разборе отказоустойчивости маршрутизации запросов к нескольким внешним моделям: важно не отключать весь пул из-за сбоя одной конкретной цели и корректно пробовать резервный маршрут. Это общий инженерный контекст работы с внешними зависимостями, без привязки к внутренним системам.
Краткое резюме
- Circuit breaker временно прекращает обращения к зависимости, которая систематически ошибается.
- Три состояния — closed, open и half-open — позволяют обнаружить сбой, дать системе передышку и осторожно проверить восстановление.
- Breaker дополняет timeout, retry, bulkhead и fallback, но не заменяет их.
- Классифицируй ошибки и настрой окна осмысленно: пользовательские ошибки не должны открывать предохранитель.
- Для операций записи учитывай неопределённость после таймаута и защищайся от дубликатов.