Circuit Breaker можно представить как три состояния, через которые он проходит в процессе работы:
- Closed (замкнутое состояние): Все запросы пропускаются. Если начинают происходить ошибки, Circuit Breaker переходит в состояние Open.
- Open (разомкнутое состояние): Запросы блокируются. Это состояние активируется, когда система обнаруживает, что зависимость не работает должным образом.
- Half-Open (полуоткрытое состояние): Открывается небольшой «лаз», через который пропускаются несколько запросов, чтобы проверить, восстановилась ли зависимость. Если запросы успешны, Circuit Breaker возвращается в состояние Closed. Если же запросы снова проваливаются — состояние возвращается в Open.
Вот простая визуализация:
Ошибки Восстановление
Closed ---------------------> Open ---------------------> Half-Open
(Таймер активен) (Проверочные запросы)
<-----------------------------------
Успехи
Инструмент для реализации: Resilience4j
В Java-приложениях реализация Circuit Breaker упрощается за счёт использования библиотеки Resilience4j. Она модульна, лёгкая, поддерживает Spring Boot и позволяет настраивать Circuit Breaker на уровне конфигурации.
Начнём с подключения зависимости Resilience4j в наш pom.xml:
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot2</artifactId>
<version>2.0.2</version>
</dependency>
Если вы используете Gradle:
implementation 'io.github.resilience4j:resilience4j-spring-boot2:2.0.2'
Настройка Circuit Breaker
Resilience4j позволяет настроить Circuit Breaker как через Java-код, так и через файл конфигурации. Мы рассмотрим оба подхода.
Добавим настройки для Circuit Breaker'а в файл application.yml:
resilience4j.circuitbreaker:
instances:
myServiceBreaker:
slidingWindowSize: 10 # Количество запросов в окне
failureRateThreshold: 50 # Порог ошибок (в процентах)
waitDurationInOpenState: 10s # Время ожидания в состоянии Open
permittedNumberOfCallsInHalfOpenState: 3 # Проверочные запросы
minimumNumberOfCalls: 5 # Минимум запросов перед активацией
automaticTransitionFromOpenToHalfOpenEnabled: true
Теперь добавим Circuit Breaker к методу, который обращается к другому сервису. Например, у нас есть микросервис, вызывающий внешний API:
@Service
public class MyService {
private final RestTemplate restTemplate;
public MyService(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
@CircuitBreaker(name = "myServiceBreaker", fallbackMethod = "fallbackResponse")
public String callExternalService() {
String url = "http://external-service/api/data";
return restTemplate.getForObject(url, String.class);
}
public String fallbackResponse(Throwable t) {
// Возвращаем простое сообщение при отказе
return "Сервис временно недоступен. Пожалуйста, попробуйте позже.";
}
}
@CircuitBreaker: Указывает, какой Circuit Breaker использовать (name).fallbackMethod: Указывает метод, который будет вызван при сбое (в данном случаеfallbackResponse).
fallbackMethod должна соответствовать сигнатуре основного метода, за исключением добавления
Throwable в конце. В противном случае Spring просто не сможет вызвать его.
Реализация Circuit Breaker с Resilience4j
Давайте создадим простой пример, чтобы продемонстрировать, как Circuit Breaker останавливает запросы, когда зависимость недоступна.
Подготовка продюсера и консьюмера Kafka
1. Продюсер:
@Service
public class EventProducer {
private final KafkaTemplate<String, String> kafkaTemplate;
public EventProducer(KafkaTemplate<String, String> kafkaTemplate) {
this.kafkaTemplate = kafkaTemplate;
}
@CircuitBreaker(name = "kafkaBreaker", fallbackMethod = "fallbackSend")
public void sendMessage(String topic, String message) {
kafkaTemplate.send(topic, message);
}
public void fallbackSend(String topic, String message, Throwable t) {
System.out.println("Не удалось отправить сообщение в Kafka. Сообщение будет отправлено позже.");
}
}
2. Консьюмер:
@Component
public class EventConsumer {
@KafkaListener(topics = "example-topic", groupId = "example-group")
public void consume(String message) {
System.out.println("Получено сообщение: " + message);
}
}
Чтобы протестировать Circuit Breaker, мы можем симулировать сбой продюсера Kafka. Например, можно остановить Kafka и проверить, как система реагирует на сбой.
- При нормальной работе вы увидите сообщения от продюсера и консьюмера:
Отправка сообщения: "Привет, Kafka!" Получено сообщение: "Привет, Kafka!" - При сбое Kafka:
Не удалось отправить сообщение в Kafka. Сообщение будет отправлено позже.
Политики отработки отказов
Resilience4j также поддерживает автоматическое повторение запросов (Retry), время ожидания (Timeout) и функционал Fallback. Эти механизмы можно комбинировать для улучшения отказоустойчивости.
Добавим настройку Retry для продюсера Kafka:
resilience4j.retry:
instances:
kafkaRetry:
maxAttempts: 5
waitDuration: 500ms
И обновим аннотацию:
@Retry(name = "kafkaRetry")
@CircuitBreaker(name = "kafkaBreaker", fallbackMethod = "fallbackSend")
public void sendMessage(String topic, String message) {
kafkaTemplate.send(topic, message);
}
Теперь система будет пытаться отправить сообщение до 5 раз перед вызовом fallbackSend.
Тестируем поведение Circuit Breaker
Используйте тестовые окружения, чтобы проверить поведение Circuit Breaker под нагрузкой и в реальных условиях отказов. Например, вы можете:
- Отключить сервис-зависимость (в нашем случае, Kafka).
- Убедиться, что Circuit Breaker входит в состояние Open.
- Проверить, что вызовы начинают возвращать данные из
fallbackMethod.
Circuit Breaker в реальной жизни
Circuit Breaker используется практически во всех реальных микросервисных системах. Например:
- В Amazon Circuit Breaker защищает микросервисы от перегрузок при пиковом спросе.
- Netflix применяет Circuit Breaker для своего потокового API, чтобы предотвратить каскадные отказы.
- В финансовых системах Circuit Breaker критически важен, чтобы избежать сбоев обработки транзакций.
Поздравляем! Теперь вы готовы защитить свои микросервисы с помощью Circuit Breaker.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ