Circuit Breaker Pattern
What is Circuit Breaker?
The Circuit Breaker Pattern prevents cascading failures by stopping requests to a failing service and providing fallback responses.
States
┌─────────┐
│ CLOSED │ ──[failures > threshold]──> ┌──────┐
│(Normal) │ │ OPEN │
└─────────┘ <──[timeout expires]──────── └──────┘
▲ │
│ │
│ ┌──────────────┐ │
└─────────│ HALF_OPEN │<────────────┘
│(Testing) │
└──────────────┘Basic Implementation
class CircuitBreaker {
constructor(fn, options = {}) {
this.fn = fn;
this.state = 'CLOSED';
this.failureCount = 0;
this.successCount = 0;
this.nextAttempt = Date.now();
this.failureThreshold = options.failureThreshold || 5;
this.successThreshold = options.successThreshold || 2;
this.timeout = options.timeout || 60000;
}
async execute(...args) {
if (this.state === 'OPEN') {
if (Date.now() < this.nextAttempt) {
throw new Error('Circuit breaker is OPEN');
}
this.state = 'HALF_OPEN';
}
try {
const result = await this.fn(...args);
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}
onSuccess() {
this.failureCount = 0;
if (this.state === 'HALF_OPEN') {
this.successCount++;
if (this.successCount >= this.successThreshold) {
this.state = 'CLOSED';
this.successCount = 0;
}
}
}
onFailure() {
this.failureCount++;
this.successCount = 0;
if (this.failureCount >= this.failureThreshold) {
this.state = 'OPEN';
this.nextAttempt = Date.now() + this.timeout;
}
}
getState() {
return this.state;
}
}Usage Example
// Wrap service call
async function callUserService(userId) {
const response = await axios.get(`http://user-service/users/${userId}`);
return response.data;
}
const breaker = new CircuitBreaker(callUserService, {
failureThreshold: 3,
successThreshold: 2,
timeout: 30000
});
// Use circuit breaker
try {
const user = await breaker.execute('user_123');
console.log(user);
} catch (error) {
console.error('Service unavailable');
}With Fallback
class CircuitBreakerWithFallback extends CircuitBreaker {
constructor(fn, fallback, options) {
super(fn, options);
this.fallback = fallback;
}
async execute(...args) {
try {
return await super.execute(...args);
} catch (error) {
if (this.state === 'OPEN') {
return await this.fallback(...args);
}
throw error;
}
}
}
// Usage
const breaker = new CircuitBreakerWithFallback(
callUserService,
async (userId) => ({ id: userId, name: 'Guest User' }),
{ failureThreshold: 3 }
);
const user = await breaker.execute('user_123');Using Opossum Library
const CircuitBreaker = require('opossum');
// Create circuit breaker
const breaker = new CircuitBreaker(callUserService, {
timeout: 3000,
errorThresholdPercentage: 50,
resetTimeout: 30000
});
// Fallback
breaker.fallback(() => ({ id: null, name: 'Guest' }));
// Events
breaker.on('open', () => {
console.log('Circuit breaker opened');
});
breaker.on('halfOpen', () => {
console.log('Circuit breaker half-open');
});
breaker.on('close', () => {
console.log('Circuit breaker closed');
});
// Execute
const user = await breaker.fire('user_123');Monitoring
class MonitoredCircuitBreaker extends CircuitBreaker {
constructor(name, fn, options) {
super(fn, options);
this.name = name;
this.metrics = {
requests: 0,
successes: 0,
failures: 0,
rejections: 0
};
}
async execute(...args) {
this.metrics.requests++;
if (this.state === 'OPEN') {
this.metrics.rejections++;
throw new Error('Circuit breaker is OPEN');
}
try {
const result = await super.execute(...args);
this.metrics.successes++;
return result;
} catch (error) {
this.metrics.failures++;
throw error;
}
}
getMetrics() {
return {
name: this.name,
state: this.state,
...this.metrics,
successRate: this.metrics.successes / this.metrics.requests,
failureRate: this.metrics.failures / this.metrics.requests
};
}
}Multiple Services
class CircuitBreakerRegistry {
constructor() {
this.breakers = new Map();
}
register(name, fn, options) {
const breaker = new CircuitBreaker(fn, options);
this.breakers.set(name, breaker);
return breaker;
}
get(name) {
return this.breakers.get(name);
}
getAll() {
return Array.from(this.breakers.entries()).map(([name, breaker]) => ({
name,
state: breaker.getState()
}));
}
}
// Setup
const registry = new CircuitBreakerRegistry();
registry.register('user-service', callUserService, {
failureThreshold: 3,
timeout: 30000
});
registry.register('order-service', callOrderService, {
failureThreshold: 5,
timeout: 60000
});
// Use
const userBreaker = registry.get('user-service');
const user = await userBreaker.execute('user_123');Health Check Endpoint
app.get('/health/circuit-breakers', (req, res) => {
const breakers = registry.getAll();
const allClosed = breakers.every(b => b.state === 'CLOSED');
res.status(allClosed ? 200 : 503).json({
status: allClosed ? 'healthy' : 'degraded',
breakers
});
});Benefits
- Prevents Cascading Failures: Stop calling failing services
- Fast Failure: Fail immediately when circuit is open
- Automatic Recovery: Test service health periodically
- Resource Protection: Prevent resource exhaustion
- Better User Experience: Provide fallback responses
Best Practices
- Set appropriate thresholds
- Implement fallbacks
- Monitor circuit state
- Log state changes
- Use different timeouts per service
- Test failure scenarios
Interview Tips
- Explain pattern: Prevent cascading failures
- Show states: Closed, Open, Half-Open
- Demonstrate implementation: Basic circuit breaker
- Discuss fallbacks: Graceful degradation
- Mention monitoring: Track metrics
- Show benefits: Fast failure, recovery
Summary
Circuit Breaker Pattern prevents cascading failures by monitoring service calls and stopping requests to failing services. Uses three states: Closed (normal), Open (failing), and Half-Open (testing recovery). Implement with failure thresholds and timeouts. Provide fallback responses for better user experience. Essential for resilient microservices.
Test Your Knowledge
Take a quick quiz to test your understanding of this topic.