В распределенных системах есть десятки паттернов для управления отказами, но на практике особенно важны те, которые помогают системе оставаться стабильной под нагрузкой. В этой статье разберем паттерн Circuit Breaker и покажем пример его реализации в коде. В следующем материале посмотрим на Fallback, Retry и Timeout — это базовые инструменты, которые помогают сделать микросервисную архитектуру устойчивее и снизить влияние сбоев на бизнес.
Чтобы не превращать материал в сухую теорию, пойдем от простого и понятного примера. Объясним, как работают эти паттерны, через аналогию с взаимодействием одного папы и его сына Егора. P.S. все герои вымышлены, совпадения случайны, а выводы — вполне рабочие.
Паттерн Circuit Breaker
Представьте: детская площадка мальчик Егор играет в песочнице с другими детьми. Его папа стоит в сторонке и наблюдает за процессом. Все прекрасно, все дети счастливы и рады. Но вдруг, между Егором и другим мальчиком начинается конфликт из-за трактора. Они никак не могут его поделить. Дети начинают злиться кричать, толкаться. Чтобы избежать дальнейшего развития конфликта, папа вмешивается и забирает Егора играть на горку. Через некоторое время, когда Егор успокоился, папа отпускает Егора снова играть в песочницу. При этом папа следит, не начнется ли снова конфликт из-за этого трактора или чего-то еще. В данном примере папа был, в роли, Circuit Breaker, который также следит за успешным взаимодействием между микросервисами.
Circuit Breaker — паттерн отказоустойчивости, который временно прекращает обращения к внешнему сервису при превышении заданного порога ошибок или медленных вызовов. Он не повторяет запросы: повторные попытки относятся к Retry.
Circuit Breaker может находиться в одном из трех состояний:
- Closed — стандартное состояние Circuit Breaker. Все запросы проходят к внешнему сервису. Если ошибки не превышают допустимый порог, состояние не меняется. Но если ошибок становится слишком много, Circuit Breaker переключается в состояние Open.
- Open — Circuit Breaker разрывает цепь. Запросы к внешнему сервису больше не отправляются, а система сразу возвращает ошибку. Это защищает и сам сервис, и инфраструктуру от лишней нагрузки. В таком состоянии Circuit Breaker находится заданное время.
- Half-Open — после истечения waitDurationInOpenState Circuit Breaker разрешает определенное количество запросов для проверки доступности внешнего сервиса. Если они проходят успешно, Circuit Breaker возвращается в состояние Closed. Если ошибки продолжаются, он снова переходит в Open.
Рисунок 1. Алгоритм работы паттерна Circuit Breaker
Более подробная информация в официальной документации.
Основные параметры для настройки Circuit Breaker
Чтобы Circuit Breaker действительно защищал систему, его важно правильно настроить. Ниже — ключевые параметры, которые определяют, когда именно он должен сработать и как вести себя после сбоя:
- failureRateThreshold — процент ошибок, после которого Circuit Breaker переходит в состояние Open.
- slowCallRateThreshold — процент медленных вызовов, при достижении которого Circuit Breaker переключается в состояние Open.
- slowCallDurationThreshold — порог времени, после которого запрос считается медленным. Если сервис формально отвечает, но делает это слишком долго, такие вызовы тоже начинают влиять на решение Circuit Breaker.
- permittedNumberOfCallsInHalfOpenState — количество разрешенных вызовов в состоянии Half-Open.
- waitDurationInOpenState — время нахождения CIRCUIT BREAKER в состоянии Open перед переключением в состояние Half-Open для проверки доступности сервиса.
- slidingWindowType — устанавливает по какой величине рассчитывается процент ошибок. Возможные значения:
COUNT_BASED — рассчитывает ошибки на основе фиксированного количества запросов.
TIME_BASED — рассчитывает ошибки в рамках временного интервала. - slidingWindowSize — количество запросов (при COUNT_BASED) или продолжительность интервала (при TIME_BASED) для расчета процента ошибок.
Грамотно настроенный Circuit Breaker помогает системе работать стабильнее: снижает риск перегрузки, ускоряет реакцию на сбои и делает поведение сервисов более предсказуемым. А значит, бизнес получает не просто техническую защиту, а более надежный пользовательский опыт и меньше потерь из-за недоступности сервисов.
Реализация паттерна управления отказами Circuit Breaker на Java
Далее — практический разбор реализации Circuit Breaker в микросервисной архитектуре на Java с помощью библиотеки Resilience4j.
Для примера выбрана именно Resilience4j: библиотека хорошо подходит для задач, связанных с устойчивостью сервисов, и при этом достаточно проста в использовании.
Среди ее особенностей:
- поддержка основных паттернов управления отказами, что позволяет закрывать несколько задач одной библиотекой;
- удобная интеграция со Spring Boot;
- простая настройка через YAML/Properties-файлы;
- модульная архитектура, благодаря которой можно подключать только нужные компоненты.
В качестве примера рассмотрим простую учебную задачу организации взаимодействия между двумя микросервисами — User и Pet. Микросервис User будет отправлять запрос в микросервис Pet для получения списка всех животных.
Чтобы сохранить фокус на теме статьи, основное внимание будет уделено именно настройке и демонстрации работы паттерна Circuit Breaker. Все аспекты, не относящиеся напрямую к теме управления отказами, будут сознательно опущены.
Подготовка микросервиса
С помощью Spring Initializr создадим основу для каждого из МС. Добавим зависимости Spring Web и Lombok. В результате pom.xml-файлы МС «User» и МС «Pet» будут идентичны.
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
С точки зрения проектирования данный запрос логичнее направлять напрямую в микросервис Pet. Но в рамках учебного проекта, чтобы максимально упростить структуру программы и сосредоточиться на паттернах ОУ будем получать всех животных через МС «User». Код контроллера МС «User».
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
@Log4j2
public class UserController {
private final UserService userService;
@GetMapping("/pets")
public ResponseEntity<List<PetDTO>> getAllPets(){
log.info("Request: From front get all pets");
List<PetDTO> petDTOList =
userService.getAllPets();
}
}
Код PetDTO представлен ниже:
@Data
public class PetDTO {
private String name;
private String petType;
private int age;
}
UserController при получении Get /api/users/pets обращается в UserService, который в свою очередь будет отправлять Get-запрос в МС «Pet» для получения списка все животных.
Код класс UserService:
@Service
@Log4j2
@RequiredArgsConstructor
public class UserService {
private final RestTemplate restTemplate;
@Value("${pet-service.url}")
private String petServiceUrl;
@Value("${pet-service.path}")
private String petServicePath;
@Value("${pet-service.getAll}")
private String petServiceGetAll;
public List<PetDTO> getAllPets(){
List<PetDTO> petDTOList = restTemplate.getForEntity(petServiceUrl+petServicePath+petServiceGetAll, List.class).getBody();
log.info("Response: From pet-service got list with all pets {}", petDTOList);
return petDTOList;
}
}
Для корректной работы МС «User» в файл application.yaml пропишем настройки порта (МС «User» работать на порту 8081) и адрес МС «Pet», по которому необходимо отправлять запрос.
Код application.yaml:
server:
port: 8081
pet-service:
url: http://localhost:8082
path: /api/pets
getAll: /all
Для отправки запросов в МС «Pet» потребуется bean RestTemplate, который создается в конфигурационном файле.
@Configuration
public class AppConfig {
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
Наш МС «User» готов к работе. Теперь напишем код для МС «Pet». Как было сказано ранее зависимости будут использоваться аналогичные зависимостям МС «User»: Spring Web, Lombok.
Код сущности Pet:
Далее создадим PetController, который будет обрабатывать Get-запрос и выдавать список всех животных.
@RestController
@RequestMapping("/api/pets")
@RequiredArgsConstructor
@Log4j2
public class PetController {
private final PetService petService;
@GetMapping("/all")
public ResponseEntity<List<Pet>> getAllPets(){
log.info("Request from user-service to get all pets");
List<Pet> petList = petService.getAllPets();
return ResponseEntity.status(HttpStatus.OK).body(petList);
}
}
Хотя это может быть излишним для данного примера, будем придерживаться канонической структуры MVC-приложения и разделять логику контроллера, бизнес-логику и логику работы с БД (хотя БД в моем примере нет). Контроллер при получении запроса, вызывает метод getAllPets сервиса, который в свою очередь обращается к методу репозитория для получения списка животных.
Код PetService:
@Service
@RequiredArgsConstructor
public class PetService {
private final PetRepository petRepository;
public List<Pet> getAllPets(){
return petRepository.getAllPets();
}
}
Так как БД не используется, в коде репозитория создадим список животных, который и будем возвращать.
Код PetRepository:
@Repository
public class PetRepository {
private static List<Pet> petList;
public PetRepository() {
petList = new ArrayList<>();
Pet pet = new Pet("Barsik", "Cat", 2);
petList.add(pet);
pet = new Pet("Sharik", "Dog", 5);
petList.add(pet);
pet = new Pet("Vasya", "Snake", 11);
petList.add(pet);
}
public List<Pet> getAllPets(){
return petList;
}
}
В файл application.yaml пропишем настройки порта 8082.
Код application.yaml:
server:
port: 8082
МС «Pet» готов. Теперь надо проверить, что все настроено и работает корректно. Для этого запускаем оба МС и отправляем Get-запрос по адресу http://localhost:8081/api/users/pets. Получаем ответ от МС «Pet», содержащий списков всех животных.
Лог МС «User»:
c.a.u.controller.UserController : Request: From front get all pets
c.a.userservice.service.UserService : Response: From pet-service got list with all pets [{name=Barsik, petType=Cat, age=2}, {name=Sharik, petType=Dog, age=5}, {name=Vasya, petType=Snake, age=11}]
Отлично! Все работает, МС обмениваются информацией.
Реализация Circuit Breaker
Теперь перейдем к самому интересному. Представим ситуацию, что МС «Pet» по каким-то причинам стал недоступен. МС «User» об этом не знает и постоянно пытается отправить запрос на получение списка животных. Подобный сценарий нельзя считать корректным: повторяющиеся безуспешные запросы лишь увеличивают нагрузку и замедляют обработку. Здесь как раз и помогает паттерн Circuit Breaker, который позволяет определить, доступен ли внешний сервис или нет.
Для реализации паттерна необходимо в МС «User» в pom-файле подключить зависимости.
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot3</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>3.3.5</version>
</dependency>
Метод getAllPets в сервисе UserService, который осуществляет отправку запросов, используем аннотацией @CircuitBreaker, чтобы активизировать данный паттерн.
@CircuitBreaker(name = "userServiceCircuitBreaker")
public List<PetDTO> getAllPets(){
log.info("user-service method getAllPets");
List<PetDTO> petDTOList = restTemplate.getForEntity(petServiceUrl+petServicePath+petServiceGetAll, List.class).getBody();
log.info("Response: From pet-service got list with all pets {}", petDTOList);
return petDTOList;
}
Настройка параметров паттерна осуществляется в файле application.yaml. Минимально необходимыми параметрами являются:
- slidingWindowType — отвечает за тип подсчитываемых величин для вычисления процента сбоев, выбираем — COUNT_BASED (количество);
- slidingWindowSize – размер выборки по которой подсчитываются ошибки, устанавливаем – 3;
- waitDurationInOpenState — время нахождения в открытом состоянии, ставим 20 сек.
resilience4j.circuitbreaker:
instances:
userServiceCircuitBreaker:
slidingWindowType: COUNT_BASED
slidingWindowSize: 3
waitDurationInOpenState: 20s
Запускаем МС «User», МС «Pet» не должен быть запущен. Делаем четыре отдельных попытки получить список всех животных. Первые три вызова завершаются неуспешно, Circuit Breaker регистрирует три неудачных результата и после этого переходит в Open. Четвертый вызов отклоняется Circuit Breaker без обращения к МС «Pet».
Log:
1. c.a.u.controller.UserController : Request: From front get all pets
c.a.userservice.service.UserService : user-service method getAllPets
o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.web.client.ResourceAccessException: I/O error on GET request for "http://localhost:8082/api/pets/all": Connection refused: connect] with root cause
2. c.a.u.controller.UserController : Request: From front get all pets
c.a.userservice.service.UserService : user-service method getAllPets
o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.web.client.ResourceAccessException: I/O error on GET request for "http://localhost:8082/api/pets/all": Connection refused: connect] with root cause
3. c.a.u.controller.UserController : Request: From front get all pets
c.a.userservice.service.UserService : user-service method getAllPets
o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.web.client.ResourceAccessException: I/O error on GET request for "http://localhost:8082/api/pets/all": Connection refused: connect] with root cause
4. c.a.u.controller.UserController : Request: From front get all pets
o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: io.github.resilience4j.circuitbreaker.CallNotPermittedException: CircuitBreaker 'userServiceCircuitBreaker' is OPEN and does not permit further calls] with root cause
В логах видно, что четвертый запрос не был выполнен, Circle Breaker заблокировал отправку запроса. По истечении 20 секунд, нахождения в открытом состоянии, МС будет снова пытаться отправлять запросы. После этого Circuit Breaker переходит в Half-Open и разрешает ограниченное количество тестовых вызовов.
Вывод
Использование паттерна Circuit Breaker позволяет сделать микросервисную архитектуру более надежной и устойчивой. Однако у данного паттерна есть и недостатки, которые важно учитывать при его применении:
- Вызывает ложные срабатывания — при неверных настройках может быстро переходить в состояние Open (например, слишком низкий процент ошибок для срабатывания), что приводит к излишним отказам сервисов, которые на самом деле могут работать стабильно.
- Прерывает нормальные запросы — когда CircuitBreaker находится в состоянии Open, все запросы к внешнему сервису будут отклоняться, включая те, которые могли бы быть успешными, если внешняя система уже восстановилась.
В первой статье разобрали, что такое управление отказами: с какими типичными проблемами сталкиваются разработчики и какие цели стоит ставить перед собой при построении устойчивых систем. Также коротко прошлись по методам и паттернам, которые помогают при создании надежных микросервисов. Переходите и читайте: «Почему один упавший сервис может обрушить всю систему: управление отказами в микросервисной архитектуре».
А в следующей статье рассмотрим паттерны Fallback, Retry и Timeout, которые помогают повысить надежность микросервисных систем.
Остаемся на связи, команда Java-разработки MediaSoft!
статьи по теме
-
ЧитатьПочему один упавший сервис может обрушить всю систему — как управлять отказами в микросервисной архитектуре. Часть 123.09.2026
-
ЧитатьКак выгодно подсветить ваш уровень владения ИИ на собеседовании08.09.2026
-
ЧитатьБольшая подборка выпусков MediaSoft Подкаст08.06.2026
-
ЧитатьJava, C#, Go, Python, PHP: что выбрать для разработки микросервисного бэкенда?02.06.2026
-
ЧитатьВ какой момент Hibernate из волшебной палочки превращается в источник проблем?26.02.2026
-
ЧитатьПрофайлинг и утечка памяти в java: Java Flight Recorder, Mission Control И Visual VM11.02.2025

