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

milinsky
22.04.2026 10:38Ох, ну и мешанина. Ну и главное вот тут:
особенно в микросервисных системах.
Представим сервис А, который обращается к сервису Б
При чём здесь микросервисы вообще? Или разъясните что вы подразумеваете вод словом “сервис”? Иначе, возникает ощущение, что вы смешали в кучу все разношёрстные знания и с уверенностью в понимании вопроса написали с помощью LLM сей пассаж.
johndow
какое гуманитарное объяснение :) circuit breaker - это же электрический предохранитель! контакт разомкнут (OPEN) - ток не идёт