Вступ
- Зачіпка:
- Почати з аналогії з реального світу: як автоматичні вимикачі в електричних системах запобігають перевантаженням.
- Пов’язати це з проблемами комунікації між мікросервісами, наприклад, каскадними збоїми.
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();
Діаграма переходів
Ось як автоматичний вимикач переходить між станами:
- Закритий → Відкритий:
Якщо рівень помилок перевищує поріг у Закритому стані, автоматичний вимикач переходить у Відкритий стан. - Відкритий → Напіввідкритий:
Після завершення тривалості очікування автоматичний вимикач переходить в Напіввідкритий стан і дозволяє обмежену кількість тестових викликів. - Напіввідкритий → Закритий:
Якщо тестові виклики успішні, автоматичний вимикач повертається в Закритий стан. - Напіввідкритий → Відкритий:
Якщо тестові виклики не вдаються, автоматичний вимикач переходить назад у Відкритий стан.
Моніторинг станів
За допомогою Resilience4j та Spring Boot можна моніторити стани автоматичного вимикача через кінцеві точки Actuator або інтегрувати інструменти моніторингу, такі як Prometheus та Grafana.
- Використовуйте кінцеву точку /actuator/circuitbreakers для отримання інформації про виконання.
- Метрики включають:
- Кількість успішних і неуспішних викликів.
- Поточний стан (Закритий, Відкритий, Напіввідкритий).
- Рівень помилок та інші пороги.
Переваги переходів між станами
- Закритий: Запобігає непотрібним перервам, контролюючи лише помилки.
- Відкритий: Швидко ізолює збоєвий сервіс, захищаючи системи, що звертаються до нього.
- Напіввідкритий: Дозволяє безпечно тестувати відновлення сервісу без ризику перевантаження системи.
3. Реалізація автоматичних вимикачів у Java
Використання Resilience4j
- Огляд 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
- Додати залежності:
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
- Юніт-тестування:
- Використання тестових утиліт 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