Реалізація Circuit Breaker для стійкості мікросервісів
Circuit Breaker (автоматичний вимикач) захищає мікросервіс від каскадних відмов: якщо залежний сервіс перевантажений або недоступний, замість нескінченних спроб повтору та накопичення запитів в очікуванні — швидкий отказ з заготовленим fallback.
Три стани
Closed (норма) — запити проходять. Лічильник помилок зростає при невдачах.
Open (вимкнено) — при перевищенні порога помилок (наприклад, 5 з 10 за 30 сек) Circuit Breaker "відкривається". Усі запити негайно відхиляються без звращення до сервісу.
Half-Open (тестування) — через timeout (наприклад, 30 сек) пропускається один тестовий запит. Якщо успішний — перехід у Closed. Якщо ні — назад у Open.
Реалізація через Opossum (Node.js)
import CircuitBreaker from 'opossum';
const paymentServiceOptions = {
timeout: 3000, // 3 сек — запит вважається зависнутим
errorThresholdPercentage: 50, // 50% помилок → Open
resetTimeout: 30000, // через 30 сек → Half-Open
volumeThreshold: 10, // мінімум 10 запитів для оцінки
};
const breaker = new CircuitBreaker(callPaymentService, paymentServiceOptions);
// Fallback коли circuit відкритий
breaker.fallback(() => ({
status: 'payment_deferred',
message: 'Платіж буде оброблено пізніше'
}));
// Моніторинг
breaker.on('open', () => logger.warn('Payment service circuit OPEN'));
breaker.on('halfOpen', () => logger.info('Payment service circuit HALF-OPEN'));
breaker.on('close', () => logger.info('Payment service circuit CLOSED'));
// Використання
async function processPayment(orderId: string, amount: number) {
return breaker.fire(orderId, amount);
}
Resilience4j (Java/Spring Boot)
@Service
public class OrderService {
@CircuitBreaker(name = "paymentService", fallbackMethod = "paymentFallback")
@Retry(name = "paymentService")
@TimeLimiter(name = "paymentService")
public CompletableFuture<PaymentResult> processPayment(Order order) {
return CompletableFuture.supplyAsync(() ->
paymentClient.charge(order.getId(), order.getTotal())
);
}
private CompletableFuture<PaymentResult> paymentFallback(Order order, Exception ex) {
log.warn("Payment service unavailable for order {}", order.getId());
return CompletableFuture.completedFuture(
PaymentResult.deferred(order.getId())
);
}
}
# application.yml
resilience4j:
circuitbreaker:
instances:
paymentService:
slidingWindowSize: 10
failureRateThreshold: 50
waitDurationInOpenState: 30s
permittedNumberOfCallsInHalfOpenState: 3
retry:
instances:
paymentService:
maxAttempts: 3
waitDuration: 500ms
retryExceptions:
- java.net.ConnectException
- java.util.concurrent.TimeoutException
Polly (.NET)
var circuitBreakerPolicy = Policy
.Handle<HttpRequestException>()
.OrResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
.CircuitBreakerAsync(
handledEventsAllowedBeforeBreaking: 5,
durationOfBreak: TimeSpan.FromSeconds(30),
onBreak: (result, duration) =>
logger.Warning("Circuit broken for {Duration}", duration),
onReset: () => logger.Information("Circuit reset")
);
var retryPolicy = Policy
.Handle<HttpRequestException>()
.WaitAndRetryAsync(3, attempt => TimeSpan.FromMilliseconds(200 * attempt));
var policy = Policy.WrapAsync(retryPolicy, circuitBreakerPolicy);
var result = await policy.ExecuteAsync(() =>
httpClient.GetAsync($"{paymentServiceUrl}/charge")
);
Метрики Circuit Breaker
Стан потрібно експортувати в Prometheus:
const openCircuits = new Gauge({
name: 'circuit_breaker_open_total',
help: 'Number of open circuit breakers',
labelNames: ['service']
});
breaker.on('open', () => openCircuits.inc({ service: 'payment' }));
breaker.on('close', () => openCircuits.dec({ service: 'payment' }));
Терміни реалізації
- Circuit Breaker для одного сервісу + fallback + метрики — 2–3 дні
- Повне покриття всіх зовнішніх викликів у сервісі + дашборд — 1 тиждень







