Оволодіння шаблоном Circuit Breaker у мікросервісах з Java: Техніки для сучасної стійкості

pic

Вступ

  1. Зачіпка:
  • Почати з аналогії з реального світу: як автоматичні вимикачі в електричних системах запобігають перевантаженням.
  • Пов’язати це з проблемами комунікації між мікросервісами, наприклад, каскадними збоїми.

2. Формулювання проблеми:

  • Чому надійність критично важлива в мікросервісах.
  • Вплив збоїв системи та як неконтрольовані збої можуть поширюватися.

3. Попередній огляд:

  • Ознайомити з патерном автоматичних вимикачів (Circuit Breaker) як рішення.
  • Зазначити, що буде досліджено сучасні техніки та реальні впровадження з використанням Java.

1. Чому автоматичні вимикачі важливі в мікросервісах

  • Обговорити характеристики мікросервісів, які потребують стійкості до збоїв (наприклад, ненадійність мережі, розподілені залежності).
  • Реальні сценарії: каскадні збої та їхні наслідки (наприклад, збої популярних платформ).
  • Необхідність швидкого відновлення та елегантного зниження функціональності при збоях.

2. Патерн автоматичного вимикача: Огляд

  • Визначення патерну автоматичного вимикача:

Роль: моніторинг залежностей сервісів і припинення непотрібних викликів під час збоїв.

Стани: Закритий, Відкритий, Напіввідкритий.

  • Діаграма: проста блок-схема, що пояснює перехід між станами.
  • Переваги:

Ізоляція збоїв.

Покращена стабільність системи.

Покращений досвід користувача під час відмов.

Закритий стан

Визначення:
Автоматичний вимикач перебуває в Закритому стані, коли система працює нормально. Виклики до залежного сервісу дозволяються як зазвичай.

Основні характеристики:

  • Автоматичний вимикач моніторить рівень успішних і неуспішних викликів до сервісу.
  • Встановлюється поріг помилок (наприклад, 50%), щоб відстежувати відсоток невдалих викликів.
  • Якщо відсоток невдалих викликів перевищує поріг у межах певного часу або кількості викликів (ковзаюче вікно), автоматичний вимикач переходить у Відкритий стан.

Приклад сценарію:

  • Сервіс A викликає сервіс B.
  • Якщо більшість викликів до сервісу B успішні, автоматичний вимикач залишається закритим.
  • Невдалі виклики враховуються, але не призводять до негайного блокування трафіку, поки рівень помилок не перевищить поріг.

Приклад налаштувань:

resilience4j:  
 circuitbreaker:  
 configs:  
 default:  
 failureRateThreshold: 50 # Перехід в Open, якщо 50% викликів не вдалось  
 slidingWindowType: COUNT_BASED  
 slidingWindowSize: 10 # Оцінка останніх 10 викликів

Відкритий стан

Визначення:
Автоматичний вимикач переходить в Відкритий стан, коли рівень помилок перевищує встановлений поріг. У цьому стані виклики до сервісу негайно блокуються і завершуються з помилкою без очікування тайм-аутів.

Основні характеристики:

  • Автоматичний вимикач тимчасово припиняє всі виклики до збоєвого сервісу.
  • Це запобігає додатковому навантаженню на сервіс і знижує споживання ресурсів (наприклад, потоки, пам’ять) у викликаючій стороні.
  • Після налаштованої "тривалості очікування" автоматичний вимикач переходить у Напіввідкритий стан, щоб перевірити, чи відновився сервіс.

Приклад сценарію:

  • Сервіс B починає стикатися з частими збоїв через збої в базі даних.
  • Автоматичний вимикач у сервісі A виявляє 60% рівень помилок (поріг перевищено).
  • Виклики до сервісу B блокуються на 60 секунд (налаштована тривалість очікування).

Приклад налаштувань:

resilience4j:  
 circuitbreaker:  
 configs:  
 default:  
 waitDurationInOpenState: 60s # Блокувати виклики протягом 60 секунд

Приклад коду:

CircuitBreakerConfig config = CircuitBreakerConfig.custom()  
 .failureRateThreshold(50)  
 .waitDurationInOpenState(Duration.ofSeconds(60))  
 .build();

Напіввідкритий стан

Визначення:
Після "тривалості очікування" у Відкритому стані, автоматичний вимикач переходить у Напіввідкритий стан.
В цьому стані дозволяється обмежена кількість викликів, щоб "перевірити", чи відновився залежний сервіс.

Основні характеристики:

  • Автоматичний вимикач дозволяє кілька тестових викликів до сервісу.
  • Якщо ці виклики успішні, автоматичний вимикач повертається в Закритий стан, і нормальна робота відновлюється.
  • Якщо тестові виклики не вдаються, автоматичний вимикач повертається в Відкритий стан і знову блокує виклики.

Приклад сценарію:

  • Після 60 секунд у Відкритому стані, Сервіс A дозволяє кілька тестових викликів до Сервісу B.
  • Якщо тестові виклики успішні (наприклад, база даних Сервісу B знову працює), трафік відновлюється.
  • Якщо тестові виклики не вдаються, Сервіс A знову припиняє виклики і повертається в Відкритий стан.

Приклад налаштувань:

resilience4j:  
 circuitbreaker:  
 configs:  
 default:  
 permittedNumberOfCallsInHalfOpenState: 5 # Дозволити 5 тестових викликів

Приклад коду:

CircuitBreakerConfig config = CircuitBreakerConfig.custom()  
 .permittedNumberOfCallsInHalfOpenState(5)  
 .build();

Діаграма переходів

Ось як автоматичний вимикач переходить між станами:

  1. Закритий → Відкритий:
    Якщо рівень помилок перевищує поріг у Закритому стані, автоматичний вимикач переходить у Відкритий стан.
  2. Відкритий → Напіввідкритий:
    Після завершення тривалості очікування автоматичний вимикач переходить в Напіввідкритий стан і дозволяє обмежену кількість тестових викликів.
  3. Напіввідкритий → Закритий:
    Якщо тестові виклики успішні, автоматичний вимикач повертається в Закритий стан.
  4. Напіввідкритий → Відкритий:
    Якщо тестові виклики не вдаються, автоматичний вимикач переходить назад у Відкритий стан.

Моніторинг станів

За допомогою Resilience4j та Spring Boot можна моніторити стани автоматичного вимикача через кінцеві точки Actuator або інтегрувати інструменти моніторингу, такі як Prometheus та Grafana.

  • Використовуйте кінцеву точку /actuator/circuitbreakers для отримання інформації про виконання.
  • Метрики включають:
  • Кількість успішних і неуспішних викликів.
  • Поточний стан (Закритий, Відкритий, Напіввідкритий).
  • Рівень помилок та інші пороги.

Переваги переходів між станами

  1. Закритий: Запобігає непотрібним перервам, контролюючи лише помилки.
  2. Відкритий: Швидко ізолює збоєвий сервіс, захищаючи системи, що звертаються до нього.
  3. Напіввідкритий: Дозволяє безпечно тестувати відновлення сервісу без ризику перевантаження системи.

3. Реалізація автоматичних вимикачів у Java

Використання Resilience4j

  1. Огляд Resilience4j:
  • Легка бібліотека для забезпечення стійкості до збоїв у Java.
  • Обговорення її модульності (CircuitBreaker, RateLimiter, Retry).

2. Покрокова реалізація:

  • Налаштування: Додавання залежностей.
implementation 'io.github.resilience4j:resilience4j-circuitbreaker:1.7.1'  
implementation 'org.springframework.boot:spring-boot-starter-aop'

Конфігурація: Використання файлу application.yaml для налаштування порогів.

resilience4j:  
 circuitbreaker:  
 configs:  
 default:  
 slidingWindowType: TIME_BASED  
 slidingWindowSize: 100  
 failureRateThreshold: 50  
 waitDurationInOpenState: 60s

Реалізація: Обгортання викликів сервісів автоматичним вимикачем.

CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("myService");  
Supplier decoratedSupplier =   
 CircuitBreaker.decorateSupplier(circuitBreaker, service::call);  

Try result = Try.ofSupplier(decoratedSupplier)  
 .recover(throwable -> "Fallback response");

3.
Моніторинг
:

  • Інтеграція з кінцевими точками Actuator для отримання метрик в реальному часі.
  • Використання Micrometer для детальної аналітики.

Використання Spring Cloud Circuit Breaker

  • Коротке порівняння з Resilience4j.
  • Підкреслення простоти використання для користувачів Spring Boot (побудовано на Resilience4j або Hystrix).
  • Приклад коду:
@RestController  
public class MyController {  
 @CircuitBreaker(name = "myService", fallbackMethod = "fallback")  
 public String callService() {  
 return myServiceClient.call();  
 }  
}

Давайте практикувати цю частину

Структура проєкту:

src/main/java/com/example/circuitbreaker  
├── config  
│ └── Resilience4jConfig.java  
├── controller  
│ └── MyController.java  
├── service  
│ ├── MyService.java  
│ └── MyServiceFallback.java  
└── CircuitBreakerApplication.java  
src/main/resources  
├── application.yml
  1. Додати залежності:

Maven



 org.springframework.boot  
 spring-boot-starter-web  


 org.springframework.boot  
 spring-boot-starter-aop  


 io.github.resilience4j  
 resilience4j-circuitbreaker  
 1.7.1  


 io.github.resilience4j  
 resilience4j-micrometer  
 1.7.1  


 io.micrometer  
 micrometer-registry-prometheus  


 org.springframework.boot  
 spring-boot-starter-actuator  


Gradle

implementation 'org.springframework.boot:spring-boot-starter-web'  
implementation 'org.springframework.boot:spring-boot-starter-aop'  
implementation 'io.github.resilience4j:resilience4j-circuitbreaker:1.7.1'  
implementation 'io.github.resilience4j:resilience4j-micrometer:1.7.1'  
implementation 'io.micrometer:micrometer-registry-prometheus'  
implementation 'org.springframework.boot:spring-boot-starter-actuator'

2. Конфігурація (application.yml)

server:  
 port: 8080  

resilience4j:  
 circuitbreaker:  
 configs:  
 default:  
 slidingWindowType: TIME_BASED  
 slidingWindowSize: 100  
 failureRateThreshold: 50  
 waitDurationInOpenState: 10s  
 instances:  
 myService:  
 baseConfig: default

3. Spring Boot застосунок

CircuitBreakerApplication.java

package com.atk.circuitbreaker;  

import org.springframework.boot.SpringApplication;  
import org.springframework.boot.autoconfigure.SpringBootApplication;  

@SpringBootApplication  
public class CircuitBreakerApplication {  
 public static void main(String[] args) {  
 SpringApplication.run(CircuitBreakerApplication.class, args);  
 }  
}

4. Шар сервісів

MyService.java

package com.atk.circuitbreaker.service;  

import org.springframework.stereotype.Service;  

@Service  
public class MyService {  

 public String call() {  
 // Симуляція помилки  
 if (Math.random() > 0.5) {  
 throw new RuntimeException("Помилка сервісу");  
 }  
 return "Успішний відповідь від MyService!";  
 }  
}

MyServiceFallback.java

package com.atk.circuitbreaker.service;  

import org.springframework.stereotype.Component;  

@Component  
public class MyServiceFallback {  

 public String fallback(Exception e) {  
 return "Резервна відповідь через: " + e.getMessage();  
 }  
}

5.
Шар контролера

MyController.java

package com.atk.circuitbreaker.controller;  

import com.atk.circuitbreaker.service.MyService;  
import com.atk.circuitbreaker.service.MyServiceFallback;  
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;  
import org.springframework.web.bind.annotation.GetMapping;  
import org.springframework.web.bind.annotation.RestController;  

@RestController  
public class MyController {  

 private final MyService myService;  
 private final MyServiceFallback myServiceFallback;  

 public MyController(MyService myService, MyServiceFallback myServiceFallback) {  
 this.myService = myService;  
 this.myServiceFallback = myServiceFallback;  
 }  

 @GetMapping("/service")  
 @CircuitBreaker(name = "myService", fallbackMethod = "fallback")  
 public String callService() {  
 return myService.call();  
 }  

 public String fallback(Exception e) {  
 return myServiceFallback.fallback(e);  
 }  
}

6. Клас конфігурації

Resilience4jConfig.java

package com.atk.circuitbreaker.config;  

import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;  
import org.springframework.context.annotation.Bean;  
import org.springframework.context.annotation.Configuration;  

import java.time.Duration;  

@Configuration  
public class Resilience4jConfig {  

 @Bean  
 public CircuitBreakerConfig customCircuitBreakerConfig() {  
 return CircuitBreakerConfig.custom()  
 .failureRateThreshold(50)  
 .waitDurationInOpenState(Duration.ofSeconds(10))  
 .slidingWindowSize(100)  
 .build();  
 }  
}

7. Моніторинг та метрики

Увімкніть кінцеві точки Actuator в application.yml:

management:  
 endpoints:  
 web:  
 exposure:  
 include: "*"

Отримання метрик Circuit Breaker:

Перейдіть за адресою /actuator/metrics/resilience4j.circuitbreaker.calls.

Інтеграція з Prometheus для детальних панелей моніторингу.

8. Тестування

Запустіть застосунок і зверніться до кінцевої точки:

curl http://localhost:8080/service

Спостерігайте за:

  • Успішними та невдалими відповідями.
  • Поведінкою резервного механізму під час симульованих помилок.

Тепер ви можете моніторити застосунок за допомогою кінцевих точок Actuator або Prometheus.

4. Просунуті техніки з Circuit Breakers

Динамічна конфігурація з Spring Cloud Config

  • Використання централізованої конфігурації для динамічного налаштування порогів Circuit Breaker.
  • Приклад використання: адаптація порогів під час подій високого трафіку.

Комбінування Circuit Breakers з Bulkheads

  • Пояснення, як обмежити використання ресурсів за допомогою Bulkheads для підвищення стійкості.

Лімітинг швидкості та тайм-аутів

  • Використання RateLimiter (від Resilience4j) разом з Circuit Breaker для запобігання перевантаженням.
  • Встановлення тайм-аутів для обмеження тривалості викликів сервісів.

Інтеграція з Service Mesh

  • Опис того, як сучасні Service Mesh, такі як Istio чи Linkerd, можуть доповнювати Circuit Breakers.

5. Тестування реалізації Circuit Breaker

  1. Юніт-тестування:
  • Використання тестових утиліт Resilience4j для перевірки переходів між станами.

2. Інтеграційне тестування:

  • Симуляція помилок за допомогою інструментів, таких як WireMock.

3. Навантажувальне тестування:

  • Використання інструментів, таких як Apache JMeter або Gatling, для стрес-тестування вашої системи.

6. Реальні кейси та отримані уроки

  • Підкреслення історій успіху (наприклад, Netflix, платформи електронної комерції).
  • Обговорення поширених проблем, таких як занадто агресивні пороги або ігнорування стратегій резервного копіювання.

7. Висновки та майбутнє Circuit Breakers

  • Підсумок важливості Circuit Breakers для мікросервісів.
  • Згадка нових тенденцій, таких як управління стійкістю, засноване на штучному інтелекті.
  • Заохочення читачів почати з малого, але ретельно тестувати в реальних сценаріях.

Перекладено з: Mastering the Circuit Breaker Pattern in Microservices with Java: Techniques for Modern Resiliency

Leave a Reply

Your email address will not be published. Required fields are marked *