Представьте: сервис А звонит сервису Б, а тот зависает. Сервис А ждёт, занимает потоки, не освобождает ресурсы. Потом к нему приходит другой сервис — и тоже встаёт в очередь. Так один сбой разрастается по всей системе, как снежный ком. Этот эффект называется каскадным отказом.

Паттерн Circuit Breaker (предохранитель) решает эту проблему. В статье разбираем его на примере ассистента HR с зонтиком, показываем, как настроить Resilience4j, и делимся, какие ошибки стоит (а какие не стоит) учитывать в статистике.

 Описание

Паттерн Circuit Breaker (предохранитель) занимает важное место среди паттернов архитектуры приложений, особенно в микросервисных системах.

В чем его суть. Представим сервис А, который обращается к сервису Б. Сервис Б по каким-то причинам начинает плохо себя вести: долго отвечать на запросы или отвечать ошибкой — например, потерял соединение с базой данных. Тогда начинает «страдать» сервис А: он вынужден долго ждать на каждом запросе, занимая ресурсы — свободные потоки, соединения с БД, удерживая транзакции открытыми.

Проблема распространяется и умножается на всю систему. У сервиса А занимается всё больше потоков, которые ничего не делают, а просто ждут. Если будут заняты все потоки, сервис А станет полностью неработоспособен. Так проблема разрастается по цепочке — этот эффект называется каскадным отказом (cascading failure).

Чтобы решить проблему, сервис А должен иметь защитный механизм, который определяет, что сервис Б сейчас в аварийном состоянии, и временно не обращаться к нему. Этот механизм и называется Circuit Breaker (предохранитель).
 

Аналогия из жизни

Чтобы лучше представить суть подхода, обратимся к аналогии.

Представьте кабинет HR, где сидит директор, отвечающий за согласование отпусков. Сотрудники заходят в кабинет, задают вопрос: «Могу ли я пойти в отпуск между майскими?», получают ответ «да» или «нет» и уходят.

Если HR устаёт и начинает медленно отвечать, коридор постепенно переполняется ожидающими. А их, согласитесь, всегда хватает :). Это затрудняет перемещение других сотрудников, которые просто хотят налить себе кофе из автомата рядом с кабинетом.

Предохранителем здесь выступает ассистент HR. Он стоит у входа и пропускает сотрудников, устанавливая правило: «Время встречи — не более  двух минут». Если ассистент замечает, что количество тех, кто находится в кабинете дольше двух минут, выросло, он запрещает вход, вешая табличку «Не беспокоить».

 

Сотрудники не ждут в очереди, а идут дальше по своим делам. Да, они не получили желаемого прямо сейчас, но это мелочь. Главное — коридор остаётся свободным. HR отдыхает 5 минут и с новыми силами снова принимает посетителей. Проблема остаётся локальной и не распространяется на всё здание корпорации.

Состояния Circuit Breaker

У Circuit Breaker (далее — CB) две задачи:

Следить за общением между клиентом и сервисом

Анализировать, происходит ли деградация

У CB есть определённое окно, например последние 30 секунд. CB считает количество «сбойных» ответов за это время, и если оно превышает порог (скажем, 30%), CB решает, что сервис Б нестабилен, перекрывает доступ и начинает сразу отвечать ошибкой «временно недоступно».

Состояние OPEN — CB блокирует все вызовы к сервису Б.Лирическое отступление: для меня долгое время было странным, почему состояние называется OPEN. Ведь логичнее было бы назвать CLOSED или BLOCKED — CB же перекрывает доступ. Но если представить, что CB раскрывается, как зонтик, чтобы защитить сервис, всё встаёт на места. Представьте ассистента HR, который встаёт поперёк двери и раскрывает зонтик перед собой..

В состоянии OPEN запросы к сервису Б не поступают. У сервиса есть время «прийти в себя»: завершить зависшие операции, а если это Kubernetes — под может перезагрузиться. В OPEN CB находится заданное время, например 5 минут.

Состояние HALF_OPEN — через 5 минут CB (ассистент) начинает пропускать сотрудников, но более внимательно следит за ситуацией, чтобы в случае чего сработать быстрее. HALF_OPEN — это способ CB понять, решилась ли проблема, ведь у CB нет обратной связи от сервиса Б в привычном смысле. HR не может позвонить ассистенту и сказать: «Всё, брат, я в порядке, запускай». CB может только приоткрыться и на опыте выполнения запросов проверить, есть ли ошибки.

В этом режиме CB, как правило, строже и возвращается в OPEN, если видит несколько ошибок. В HALF_OPEN он находится определённое время (скажем, минуту). Если всё хорошо — он полностью открывает проход, переходя в состояние CLOSED (нормальный режим), продолжая наблюдать за качеством коммуникации.

Обработка разных ошибок

Представьте, что ассистент понимает только язык ошибок. Он не «засекает» время, которое сотрудник провёл в кабинете. Сотрудник сам выходит по истечении двух минут с жёлтой карточкой «Не дождался ответа» — это, говоря IT-языком, 504 Timeout.

Есть и другие варианты. Сотрудник заходит в кабинет и видит HR, лежащего на полу в обмороке от усталости. В страхе выбегает с криком и жёлтой карточкой «HR упал» — это 502 Bad Gateway. На такую ошибку ассистент должен реагировать, приводя «зонтик-предохранитель» в OPEN.

Но что, если ошибка другого рода? Сотрудник злоупотребил отпусками и вышел с синей карточкой «Уволен». Ассистент не должен учитывать такие ошибки. Это ошибка бизнес-логики, она говорит о нарушении бизнес-процесса, но не о деградации способности HR выполнять свою работу.

Вывод: критически важно объяснить CB, какие ошибки он должен отслеживать, а какие — игнорировать.

Как это выглядит в коде

Перенесём аналогию в код. У нас есть:

Сервис «Портал» — получает информацию о наличии отпусков

Сервис «Отпусков» — два разных микросервиса, взаимодействующие через REST API

В коде Портала есть точка входа в сервис Отпусков, которая скрывает конкретную реализацию HTTP-клиента. HTTP-клиент умеет возвращать разные ошибки, в том числе Timeout Exception. CB встраивается как декоратор над HTTP-клиентом.

 

 Для примера возьмём популярную библиотеку Resilience4j, которая реализует Circuit Breaker.

Основные параметры которые нужно знать:

slidingWindowSize

Скользящее окно для подсчёта ошибок. Это не временное окно, а количество вызовов (например, последние 10 вызовов)

failureRateThreshold

Процент сбоев в окне. При пересечении порога CB переходит в OPEN

minimumNumberOfCalls

Минимальное количество вызовов в окне, чтобы CB начал учитывать статистику. Нужен, чтобы CB не принимал поспешных решений на основе слишком маленькой выборки

waitDurationInOpenState

Сколько секунд CB блокирует запросы (например, 60 секунд)

permittedNumberOfCallsInHalfOpenState

Количество вызовов, которое CB пропускает в режиме HALF_OPEN

recordException

Типы ошибок, которые считаются сбойными и учитываются в статистике

 Стратегия настроек

«Строгий» CB

slidingWindowSize: 10

failureRateThreshold: 50

minimumNumberOfCalls: 5

waitDurationInOpenState: 20

permittedNumberOfCallsInHalfOpenState: 2

будет:

быстро реагировать на проблемы (4 сбойных вызова из 10 — и OPEN)

ждать 20 секунд

в HALF_OPEN быть чувствительным (2 сбоя — и снова OPEN)

Такой вариант подходит для средненагруженных сервисов, чувствительных к задержкам. Мы быстро отключаем проблемный бэкенд, но не дёргаемся на каждый «чих» благодаря minimumNumberOfCalls.

Если нужно сделать мягче — увеличиваем окно до 50, а minimumNumberOfCalls до 10.

⚠️ Нет идеальных настроек. Для каждого сервиса параметры подбираются индивидуально.

На что обращать внимание:

  • waitDurationInOpenState зависит от времени восстановления сбойного сервиса. Если восстановление — это перезагрузка пода в Kubernetes, 20 секунд может не хватить. Нужно учесть время старта пода и readiness probe.

  • Наличие Fallback. Fallback — паттерн, который часто идёт рука об руку с CB. При сбое мы не отдаём клиенту ошибку, а направляем его по альтернативному маршруту (например, в кеширующий сервис вместо основного).

  • Внешние API могут восстанавливаться непредсказуемо — это вынудит увеличить waitDurationInOpenState.

  • Требования бизнеса. Для критичных сервисов (например, оплата) делаем строгий порог: 30% сбоев — и OPEN. Для некритичных (комментарии к заказу) — либеральнее: 80%. «Ну не показали сейчас новые комментарии, ничего страшного, потом покажем».

  • minimumNumberOfCalls зависит от ожидаемого трафика. При RPS 1000 значение 5 будет «пылинкой» — CB будет срабатывать постоянно, как дребезг контактов. Для высоконагруженных сервисов значение нужно увеличивать.

Восстановление после сбоя

Как убедиться, что сервис действительно восстановился?

Если сервис упал от перегрузки, пускать в HALF_OPEN сразу 2 вызова может быть рискованно. Лучше: пускать их последовательно (одним потоком) или установить permittedNumberOfCallsInHalfOpenState = 1

Для сервиса с долгими запросами (например, 5 секунд на ответ) 2 пробных вызова за период HALF_OPEN — нормально.

 Фильтрация ошибок

Очень важно понимать, для каких типов исключительных ситуаций подходит Circuit Breaker.

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

По нашему опыту, в списке recordException должны быть только две стандартные ошибки: Timeout и 502 Bad Gateway. Всё остальное — бизнес-ошибки или проблемы, которые CB не лечит.

 Итог:

Что мы поняли за это время? Во-первых, ассистент должен знать, какие карточки считать сбойными (504, 502), а какие — просто странностями бизнеса (синяя карточка «Уволен»). Во-вторых, у него должно быть достаточно терпения (minimumNumberOfCalls), чтобы не паниковать после первого же чиха. И в-третьих, он должен уметь вовремя открывать зонтик (OPEN), давать HR отдохнуть ровно столько, сколько нужно (waitDurationInOpenState), а потом осторожно пускать одного-двух смельчаков, чтобы проверить, не рухнул ли HR в обморок снова (HALF_OPEN).

Circuit Breaker не делает систему идеальной. Он делает её честной: вместо бесконечного ожидания в коридоре вы получаете быстрый ответ «временно недоступно». И это, согласитесь, лучше, чем парализованное здание корпорации, где никто не может налить себе кофе.

Настраивайте свои предохранители с умом, мониторьте их через Actuator и помните: хороший ассистент — это тот, кто вовремя закрывает дверь, но всегда готов открыть её, когда всё наладится.

Комментарии (2)


  1. johndow
    22.04.2026 10:38

    Б.Лирическое отступление: для меня долгое время было странным, почему состояние называется OPEN. Ведь логичнее было бы назвать CLOSED или BLOCKED — CB же перекрывает доступ. Но если представить, что CB раскрывается, как зонтик, чтобы защитить сервис, всё встаёт на места. Представьте ассистента HR, который встаёт поперёк двери и раскрывает зонтик перед собой…

    какое гуманитарное объяснение :) circuit breaker - это же электрический предохранитель! контакт разомкнут (OPEN) - ток не идёт


  1. milinsky
    22.04.2026 10:38

    Ох, ну и мешанина. Ну и главное вот тут:

    особенно в микросервисных системах.

    Представим сервис А, который обращается к сервису Б

    При чём здесь микросервисы вообще? Или разъясните что вы подразумеваете вод словом “сервис”? Иначе, возникает ощущение, что вы смешали в кучу все разношёрстные знания и с уверенностью в понимании вопроса написали с помощью LLM сей пассаж.