Реализация Circuit Breaker для отказоустойчивости микросервисов
Circuit Breaker (автоматический выключатель) защищает микросервис от каскадных отказов: если зависимый сервис перегружен или недоступен, вместо бесконечных retry и накопления ожидающих запросов — быстрый отказ с заранее заготовленным 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 неделя







