Circuit Breaker Pattern
Circuit Breaker Pattern
Das Circuit Breaker Pattern verhindert Kaskadenausfälle in verteilten Systemen. Lernen Sie die States, Konfiguration und Implementierung für resiliente Anwendungen.
Das Problem
┌─────────────────────────────────────────────────────────────┐ │ KASKADEN-AUSFALL │ ├─────────────────────────────────────────────────────────────┤ │ │ │ Service A Service B Service C │ │ ┌───────┐ ┌───────┐ ┌───────┐│ │ │ │──── Call ─────►│ │──── Call ───►│ 💥 ││ │ │ │ │ │ │ DOWN ││ │ │ │◄─── Timeout ───│ │◄─── Timeout ─│ ││ │ │ │ │ │ └───────┘│ │ │ │ │ ⏳ │ │ │ │ ⏳ │ │ Wartet│ │ │ │ Wartet│ │ │ │ │ └───────┘ └───────┘ │ │ │ │ OHNE CIRCUIT BREAKER: │ │ • Threads blockieren beim Warten │ │ • Timeouts addieren sich │ │ • Ressourcen werden erschöpft │ │ • Ausfall propagiert sich nach oben │ │ │ └─────────────────────────────────────────────────────────────┘
Die Lösung: Circuit Breaker
┌─────────────────────────────────────────────────────────────┐ │ CIRCUIT BREAKER STATES │ ├─────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────────────────┐ │ │ │ │ │ │ ▼ │ │ │ ┌────────────┐ Failure │ │ │ │ CLOSED │ Threshold │ │ │ │ │ erreicht │ │ │ │ (Normal) │─────────────────┐ │ │ │ └────────────┘ │ │ │ │ ▲ │ │ │ │ │ ▼ │ │ │ Success ┌────────────┐ │ │ Threshold │ OPEN │ │ │ erreicht │ │ │ │ │ │ (Blocked) │ │ │ ┌────────────┐ └─────┬──────┘ │ │ │ HALF-OPEN │◄──────────────┘ │ │ │ │ Nach Timeout │ │ │ (Testing) │ │ │ └─────┬──────┘ │ │ │ │ │ │ Failure │ │ └──────────────────────────────►(zurück OPEN) │ │ │ │ CLOSED: Requests gehen durch │ │ OPEN: Requests werden sofort abgelehnt (Fail Fast) │ │ HALF-OPEN: Testweise einige Requests durchlassen │ │ │ └─────────────────────────────────────────────────────────────┘
Konfiguration
CIRCUIT BREAKER PARAMETER ┌────────────────────────┬────────────────────────────────────┐ │ failureThreshold │ Anzahl Fehler bis OPEN (z.B. 5) │ ├────────────────────────┼────────────────────────────────────┤ │ failureRateThreshold │ Fehler-% bis OPEN (z.B. 50%) │ ├────────────────────────┼────────────────────────────────────┤ │ successThreshold │ Erfolge bis CLOSED (z.B. 3) │ ├────────────────────────┼────────────────────────────────────┤ │ timeout │ Request Timeout (z.B. 3s) │ ├────────────────────────┼────────────────────────────────────┤ │ resetTimeout │ Zeit bis HALF-OPEN (z.B. 30s) │ ├────────────────────────┼────────────────────────────────────┤ │ slidingWindowSize │ Anzahl Requests für Berechnung │ └────────────────────────┴────────────────────────────────────┘ Beispiel-Konfiguration: • 5 Fehler in den letzten 10 Requests → OPEN • 30 Sekunden warten → HALF-OPEN • 3 erfolgreiche Requests in HALF-OPEN → CLOSED
JavaScript Implementation
// Einfache Circuit Breaker Klasse
class CircuitBreaker {
constructor(options = {}) {
this.failureThreshold = options.failureThreshold || 5;
this.successThreshold = options.successThreshold || 3;
this.resetTimeout = options.resetTimeout || 30000;
this.state = 'CLOSED';
this.failureCount = 0;
this.successCount = 0;
this.lastFailureTime = null;
}
async execute(fn) {
if (this.state === 'OPEN') {
// Prüfen ob resetTimeout abgelaufen
if (Date.now() - this.lastFailureTime >= this.resetTimeout) {
this.state = 'HALF_OPEN';
console.log('Circuit Breaker: HALF_OPEN');
} else {
throw new CircuitBreakerOpenError('Circuit is OPEN');
}
}
try {
const result = await fn();
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}
onSuccess() {
if (this.state === 'HALF_OPEN') {
this.successCount++;
if (this.successCount >= this.successThreshold) {
this.reset();
console.log('Circuit Breaker: CLOSED');
}
} else {
this.failureCount = 0; // Reset bei Erfolg
}
}
onFailure() {
this.failureCount++;
this.lastFailureTime = Date.now();
if (this.state === 'HALF_OPEN') {
this.state = 'OPEN';
this.successCount = 0;
console.log('Circuit Breaker: OPEN (from HALF_OPEN)');
} else if (this.failureCount >= this.failureThreshold) {
this.state = 'OPEN';
console.log('Circuit Breaker: OPEN');
}
}
reset() {
this.state = 'CLOSED';
this.failureCount = 0;
this.successCount = 0;
this.lastFailureTime = null;
}
getState() {
return this.state;
}
}
class CircuitBreakerOpenError extends Error {
constructor(message) {
super(message);
this.name = 'CircuitBreakerOpenError';
}
}
// Verwendung
const circuitBreaker = new CircuitBreaker({
failureThreshold: 5,
successThreshold: 3,
resetTimeout: 30000
});
async function fetchUser(userId) {
return circuitBreaker.execute(async () => {
const response = await fetch(`http://user-service/users/${userId}`, {
timeout: 3000
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response.json();
});
}
// Aufruf mit Fallback
async function getUserWithFallback(userId) {
try {
return await fetchUser(userId);
} catch (error) {
if (error instanceof CircuitBreakerOpenError) {
console.log('Circuit open, using cached data');
return getCachedUser(userId);
}
throw error;
}
}
PHP Implementation mit Opossum
// Installation: composer require leocarmo/circuit-breaker-php
use LeoCarmo\CircuitBreaker\CircuitBreaker;
// Circuit Breaker mit Redis Storage
$circuitBreaker = new CircuitBreaker(
new RedisAdapter($redis, 'user-service'),
'user-service',
[
'timeWindow' => 60, // Sliding Window (Sekunden)
'failureRateThreshold' => 50, // 50% Fehlerrate
'intervalToHalfOpen' => 30, // Zeit bis HALF-OPEN (Sekunden)
'minimumRequests' => 10, // Min. Requests für Berechnung
'successRateToClose' => 80, // 80% Erfolg → CLOSED
]
);
// Verwendung
public function getUser(int $userId): ?User
{
if (!$this->circuitBreaker->isAvailable()) {
// Circuit ist OPEN - Fallback nutzen
return $this->getCachedUser($userId);
}
try {
$response = $this->httpClient->get("/users/{$userId}");
$this->circuitBreaker->success();
return User::fromResponse($response);
} catch (Exception $e) {
$this->circuitBreaker->failure();
throw $e;
}
}
Resilience4j (Java)
// build.gradle
implementation 'io.github.resilience4j:resilience4j-circuitbreaker:2.0.0'
// Java Configuration
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
.failureRateThreshold(50) // 50% Fehlerrate
.waitDurationInOpenState(Duration.ofSeconds(30))
.permittedNumberOfCallsInHalfOpenState(3)
.slidingWindowSize(10)
.build();
CircuitBreakerRegistry registry = CircuitBreakerRegistry.of(config);
CircuitBreaker circuitBreaker = registry.circuitBreaker("userService");
// Verwendung
Supplier<User> decoratedSupplier = CircuitBreaker
.decorateSupplier(circuitBreaker, () -> userService.getUser(userId));
Try.ofSupplier(decoratedSupplier)
.recover(CircuitBreakerOpenException.class, e -> getCachedUser(userId))
.get();
// Mit Annotations (Spring)
@CircuitBreaker(name = "userService", fallbackMethod = "getCachedUser")
public User getUser(Long userId) {
return userServiceClient.getUser(userId);
}
public User getCachedUser(Long userId, Exception e) {
return userCache.get(userId);
}
Polly (.NET)
// NuGet: Polly
// Circuit Breaker Policy
var circuitBreakerPolicy = Policy
.Handle<HttpRequestException>()
.Or<TimeoutException>()
.CircuitBreakerAsync(
exceptionsAllowedBeforeBreaking: 5,
durationOfBreak: TimeSpan.FromSeconds(30),
onBreak: (exception, timespan) => {
Console.WriteLine($"Circuit opened for {timespan.Seconds}s");
},
onReset: () => {
Console.WriteLine("Circuit closed");
},
onHalfOpen: () => {
Console.WriteLine("Circuit half-open");
}
);
// Verwendung
try
{
var result = await circuitBreakerPolicy.ExecuteAsync(
async () => await httpClient.GetAsync("/api/users/1")
);
}
catch (BrokenCircuitException)
{
// Fallback
return GetCachedUser(1);
}
// Advanced: Mit Timeout kombiniert
var policy = Policy.WrapAsync(
Policy.TimeoutAsync(TimeSpan.FromSeconds(3)),
circuitBreakerPolicy
);
Monitoring
// Circuit Breaker Metriken exportieren
const circuitBreakerMetrics = {
state: 'closed', // closed, open, half_open
totalCalls: 1000,
successfulCalls: 950,
failedCalls: 50,
notPermittedCalls: 0, // Calls blocked by OPEN circuit
failureRate: 5.0, // %
slowCallRate: 2.0, // %
timeInOpenState: 0, // Sekunden
lastStateChange: '2024-01-15T10:30:00Z'
};
// Prometheus Metriken
circuit_breaker_state{service="user-service"} 1 // 1=closed, 0=open
circuit_breaker_calls_total{service="user-service",result="success"} 950
circuit_breaker_calls_total{service="user-service",result="failure"} 50
circuit_breaker_failure_rate{service="user-service"} 5.0
// Alerting
ALERT CircuitBreakerOpen
IF circuit_breaker_state == 0
FOR 1m
LABELS { severity = "warning" }
ANNOTATIONS {
summary = "Circuit breaker is OPEN",
description = "Circuit breaker for {{ $labels.service }} is open"
}
Best Practices
1. FALLBACK STRATEGIEN ┌────────────────────────────────────────────────────────┐ │ • Cached Data zurückgeben │ │ • Default Value zurückgeben │ │ • Alternative Service anfragen │ │ • Graceful Degradation (reduzierte Funktionalität) │ └────────────────────────────────────────────────────────┘ 2. RICHTIGE SCHWELLWERTE ┌────────────────────────────────────────────────────────┐ │ • Zu niedrig: Circuit öffnet zu früh │ │ • Zu hoch: Schäden bevor Circuit öffnet │ │ • Sliding Window für stabilere Berechnung │ │ • Minimum Requests bevor Entscheidung │ └────────────────────────────────────────────────────────┘ 3. KOMBINIEREN MIT ANDEREN PATTERNS ┌────────────────────────────────────────────────────────┐ │ • Retry (innerhalb Circuit Breaker) │ │ • Timeout (vor Circuit Breaker) │ │ • Bulkhead (Ressourcen isolieren) │ │ • Rate Limiting │ └────────────────────────────────────────────────────────┘ 4. MONITORING UND ALERTING ┌────────────────────────────────────────────────────────┐ │ • State-Änderungen loggen │ │ • Metriken exportieren │ │ • Alerts bei OPEN State │ │ • Dashboard für Übersicht │ └────────────────────────────────────────────────────────┘
💡 Zusammenfassung:
1. Circuit Breaker verhindert Kaskadenausfälle
2. Drei States: CLOSED → OPEN → HALF-OPEN
3. Immer Fallback-Strategie implementieren
4. Mit Timeout und Retry kombinieren
5. State-Änderungen monitoren und alerten
2. Drei States: CLOSED → OPEN → HALF-OPEN
3. Immer Fallback-Strategie implementieren
4. Mit Timeout und Retry kombinieren
5. State-Änderungen monitoren und alerten