Circuit Breaker คืออะไร?
Circuit Breaker Pattern เป็นรูปแบบการออกแบบที่ช่วยป้องกันไม่ให้ระบบหนึ่งเรียกใช้บริการที่กำลังล้มเหลวอย่างต่อเนื่อง โดยจะ “ตัดวงจร” เมื่อเกิดความผิดพลาดเกินขีดจำกัด
เหมือนกับ Circuit Breaker ในระบบไฟฟ้าที่จะตัดไฟเมื่อมีกระแสเกินเพื่อป้องกันไฟไหม้
States ของ Circuit Breaker
1. Closed State (ปกติ)
- Request ผ่านไปยัง Service ได้ปกติ
- นับจำนวน Failures
- เมื่อ Failure เกิน Threshold จะเปลี่ยนเป็น Open
2. Open State (ตัดวงจร)
- Block Request ทั้งหมด ส่ง Error กลับทันที
- มี Timeout สำหรับเปลี่ยนเป็น Half-Open
- ช่วยป้องกัน Cascade Failure
3. Half-Open State (ทดลอง)
- อนุญาต Request จำนวนจำกัด
- ถ้าสำเร็จ → กลับเป็น Closed
- ถ้าล้มเหลว → กลับเป็น Open
graph LR
A[Closed] -->|Failures > Threshold| B[Open]
B -->|Timeout| C[Half-Open]
C -->|Success| A
C -->|Failure| B
การ Implement Circuit Breaker
JavaScript/Node.js Implementation
class CircuitBreaker {
constructor(options = {}) {
this.failureThreshold = options.failureThreshold || 5;
this.resetTimeout = options.resetTimeout || 30000; // 30 seconds
this.monitor = options.monitor || console.log;
this.state = 'CLOSED';
this.failureCount = 0;
this.lastFailureTime = null;
this.nextAttempt = null;
}
async call(service) {
if (this.state === 'OPEN') {
if (Date.now() < this.nextAttempt) {
throw new Error('Circuit breaker is OPEN');
}
// Try to recover
this.state = 'HALF_OPEN';
this.monitor('Circuit breaker entering HALF_OPEN state');
}
try {
const result = await service();
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}
onSuccess() {
this.failureCount = 0;
if (this.state === 'HALF_OPEN') {
this.state = 'CLOSED';
this.monitor('Circuit breaker recovered to CLOSED state');
}
}
onFailure() {
this.failureCount++;
this.lastFailureTime = Date.now();
if (this.state === 'HALF_OPEN') {
this.state = 'OPEN';
this.nextAttempt = Date.now() + this.resetTimeout;
this.monitor('Circuit breaker back to OPEN state');
} else if (this.failureCount >= this.failureThreshold) {
this.state = 'OPEN';
this.nextAttempt = Date.now() + this.resetTimeout;
this.monitor(`Circuit breaker OPENED due to ${this.failureCount} failures`);
}
}
getState() {
return {
state: this.state,
failureCount: this.failureCount,
lastFailureTime: this.lastFailureTime,
nextAttempt: this.nextAttempt
};
}
}
การใช้งานในจริง
const axios = require('axios');
class APIService {
constructor() {
this.circuitBreaker = new CircuitBreaker({
failureThreshold: 3,
resetTimeout: 60000, // 1 minute
monitor: (message) => console.log(`[Circuit Breaker] ${message}`)
});
}
async getUserById(userId) {
try {
return await this.circuitBreaker.call(async () => {
const response = await axios.get(`/api/users/${userId}`, {
timeout: 5000
});
return response.data;
});
} catch (error) {
// Fallback strategy
return this.getUserFromCache(userId) || this.getDefaultUser();
}
}
getUserFromCache(userId) {
// Implementation for cache lookup
return cache.get(`user:${userId}`);
}
getDefaultUser() {
return {
id: 'unknown',
name: 'Anonymous User',
email: 'unknown@example.com'
};
}
}
Advanced Circuit Breaker Features
1. Sliding Window Counter
class SlidingWindowCircuitBreaker {
constructor(options = {}) {
this.windowSize = options.windowSize || 10;
this.failureThreshold = options.failureThreshold || 0.5; // 50%
this.resetTimeout = options.resetTimeout || 30000;
this.requests = [];
this.state = 'CLOSED';
}
async call(service) {
if (this.state === 'OPEN' && !this.shouldAttemptReset()) {
throw new Error('Circuit breaker is OPEN');
}
const startTime = Date.now();
try {
const result = await service();
this.recordSuccess(startTime);
return result;
} catch (error) {
this.recordFailure(startTime);
throw error;
}
}
recordSuccess(timestamp) {
this.addRequest(timestamp, true);
if (this.state === 'HALF_OPEN') {
this.state = 'CLOSED';
}
}
recordFailure(timestamp) {
this.addRequest(timestamp, false);
const failureRate = this.getFailureRate();
if (failureRate >= this.failureThreshold) {
this.state = 'OPEN';
this.openedAt = Date.now();
}
}
addRequest(timestamp, success) {
this.requests.push({ timestamp, success });
// Keep only recent requests
const cutoff = timestamp - this.windowSize * 1000;
this.requests = this.requests.filter(req => req.timestamp > cutoff);
}
getFailureRate() {
if (this.requests.length === 0) return 0;
const failures = this.requests.filter(req => !req.success).length;
return failures / this.requests.length;
}
shouldAttemptReset() {
return this.state === 'OPEN' &&
Date.now() - this.openedAt >= this.resetTimeout;
}
}
2. Multiple Failure Types
class SmartCircuitBreaker {
constructor(options = {}) {
this.thresholds = {
timeout: options.timeoutThreshold || 3,
serverError: options.serverErrorThreshold || 5,
networkError: options.networkErrorThreshold || 2
};
this.counts = {
timeout: 0,
serverError: 0,
networkError: 0
};
this.state = 'CLOSED';
}
classifyError(error) {
if (error.code === 'ECONNREFUSED' || error.code === 'ENOTFOUND') {
return 'networkError';
}
if (error.code === 'ECONNABORTED' || error.message.includes('timeout')) {
return 'timeout';
}
if (error.response && error.response.status >= 500) {
return 'serverError';
}
return 'unknown';
}
onFailure(error) {
const errorType = this.classifyError(error);
if (errorType in this.counts) {
this.counts[errorType]++;
if (this.counts[errorType] >= this.thresholds[errorType]) {
this.state = 'OPEN';
console.log(`Circuit opened due to ${errorType} errors`);
}
}
}
}
Circuit Breaker สำหรับ Database Connections
class DatabaseCircuitBreaker {
constructor(database, options = {}) {
this.database = database;
this.circuitBreaker = new CircuitBreaker({
failureThreshold: options.failureThreshold || 3,
resetTimeout: options.resetTimeout || 30000
});
}
async query(sql, params = []) {
return this.circuitBreaker.call(async () => {
const connection = await this.database.getConnection();
try {
return await connection.query(sql, params);
} finally {
connection.release();
}
});
}
async transaction(operations) {
return this.circuitBreaker.call(async () => {
const connection = await this.database.getConnection();
await connection.beginTransaction();
try {
const results = [];
for (const operation of operations) {
const result = await connection.query(operation.sql, operation.params);
results.push(result);
}
await connection.commit();
return results;
} catch (error) {
await connection.rollback();
throw error;
} finally {
connection.release();
}
});
}
}
Integration กับ HTTP Clients
Axios Interceptor
class HTTPCircuitBreaker {
constructor() {
this.breakers = new Map();
this.setupInterceptors();
}
getBreaker(baseURL) {
if (!this.breakers.has(baseURL)) {
this.breakers.set(baseURL, new CircuitBreaker({
failureThreshold: 5,
resetTimeout: 60000
}));
}
return this.breakers.get(baseURL);
}
setupInterceptors() {
// Request interceptor
axios.interceptors.request.use(
(config) => {
const breaker = this.getBreaker(config.baseURL);
if (breaker.getState().state === 'OPEN') {
return Promise.reject(new Error('Circuit breaker is OPEN'));
}
return config;
},
(error) => Promise.reject(error)
);
// Response interceptor
axios.interceptors.response.use(
(response) => {
const breaker = this.getBreaker(response.config.baseURL);
breaker.onSuccess();
return response;
},
(error) => {
if (error.config && error.config.baseURL) {
const breaker = this.getBreaker(error.config.baseURL);
breaker.onFailure();
}
return Promise.reject(error);
}
);
}
}
// การใช้งาน
const httpBreaker = new HTTPCircuitBreaker();
// ทุก HTTP request จะผ่าน Circuit Breaker อัตโนมัติ
const api = axios.create({
baseURL: 'https://api.example.com',
timeout: 5000
});
Monitoring และ Metrics
class CircuitBreakerMonitor {
constructor() {
this.metrics = {
requests: 0,
successes: 0,
failures: 0,
circuitOpens: 0,
circuitCloses: 0
};
this.events = [];
}
recordRequest() {
this.metrics.requests++;
}
recordSuccess() {
this.metrics.successes++;
this.addEvent('SUCCESS');
}
recordFailure(error) {
this.metrics.failures++;
this.addEvent('FAILURE', { error: error.message });
}
recordCircuitOpen() {
this.metrics.circuitOpens++;
this.addEvent('CIRCUIT_OPEN');
}
recordCircuitClose() {
this.metrics.circuitCloses++;
this.addEvent('CIRCUIT_CLOSE');
}
addEvent(type, data = {}) {
this.events.push({
timestamp: new Date().toISOString(),
type,
...data
});
// Keep only last 100 events
if (this.events.length > 100) {
this.events = this.events.slice(-100);
}
}
getMetrics() {
const successRate = this.metrics.requests > 0
? (this.metrics.successes / this.metrics.requests * 100).toFixed(2)
: 0;
return {
...this.metrics,
successRate: `${successRate}%`,
recentEvents: this.events.slice(-10)
};
}
// Export metrics for Prometheus
toPrometheusFormat() {
return `
# HELP circuit_breaker_requests_total Total number of requests
# TYPE circuit_breaker_requests_total counter
circuit_breaker_requests_total ${this.metrics.requests}
# HELP circuit_breaker_successes_total Total number of successful requests
# TYPE circuit_breaker_successes_total counter
circuit_breaker_successes_total ${this.metrics.successes}
# HELP circuit_breaker_failures_total Total number of failed requests
# TYPE circuit_breaker_failures_total counter
circuit_breaker_failures_total ${this.metrics.failures}
# HELP circuit_breaker_opens_total Total number of circuit opens
# TYPE circuit_breaker_opens_total counter
circuit_breaker_opens_total ${this.metrics.circuitOpens}
`.trim();
}
}
Fallback Strategies
1. Cache Fallback
class CacheFirstService {
constructor() {
this.cache = new Map();
this.circuitBreaker = new CircuitBreaker();
}
async getData(key) {
try {
// Try primary service
const data = await this.circuitBreaker.call(() =>
this.fetchFromPrimaryService(key)
);
// Cache successful result
this.cache.set(key, {
data,
timestamp: Date.now(),
ttl: 300000 // 5 minutes
});
return data;
} catch (error) {
// Fallback to cache
const cached = this.cache.get(key);
if (cached && Date.now() - cached.timestamp < cached.ttl) {
console.log('Serving from cache due to circuit breaker');
return cached.data;
}
// Last resort: default data
return this.getDefaultData(key);
}
}
}
2. Secondary Service Fallback
class MultiServiceClient {
constructor() {
this.primaryBreaker = new CircuitBreaker({ name: 'primary' });
this.secondaryBreaker = new CircuitBreaker({ name: 'secondary' });
}
async fetchUser(userId) {
try {
return await this.primaryBreaker.call(() =>
this.fetchFromPrimary(userId)
);
} catch (primaryError) {
console.log('Primary service failed, trying secondary...');
try {
return await this.secondaryBreaker.call(() =>
this.fetchFromSecondary(userId)
);
} catch (secondaryError) {
// Both services failed
throw new Error('All services unavailable');
}
}
}
}
Best Practices
1. กำหนด Threshold ที่เหมาะสม
// สำหรับ Critical Services
const criticalServiceBreaker = new CircuitBreaker({
failureThreshold: 2, // เปิดเร็ว
resetTimeout: 10000 // ลองใหม่เร็ว
});
// สำหรับ Non-Critical Services
const nonCriticalBreaker = new CircuitBreaker({
failureThreshold: 10, // อดทนมากกว่า
resetTimeout: 60000 // ลองใหม่ช้ากว่า
});
2. Health Check Integration
class HealthAwareCircuitBreaker extends CircuitBreaker {
constructor(healthCheckUrl, options = {}) {
super(options);
this.healthCheckUrl = healthCheckUrl;
this.healthCheckInterval = options.healthCheckInterval || 30000;
this.startHealthCheck();
}
startHealthCheck() {
setInterval(async () => {
if (this.state === 'OPEN') {
const isHealthy = await this.checkHealth();
if (isHealthy) {
this.state = 'HALF_OPEN';
console.log('Service appears healthy, entering HALF_OPEN');
}
}
}, this.healthCheckInterval);
}
async checkHealth() {
try {
const response = await axios.get(this.healthCheckUrl, {
timeout: 5000
});
return response.status === 200;
} catch {
return false;
}
}
}
3. Graceful Degradation
class GracefulService {
async getRecommendations(userId) {
try {
return await this.circuitBreaker.call(() =>
this.getPersonalizedRecommendations(userId)
);
} catch (error) {
console.log('Personalized recommendations unavailable, using fallback');
// Fallback to popular items
return this.getPopularRecommendations();
}
}
async getPopularRecommendations() {
// Return cached popular items or default recommendations
return this.cache.get('popular') || this.getDefaultRecommendations();
}
}
สรุป
Circuit Breaker Pattern เป็นเครื่องมือสำคัญในการสร้าง Resilient Microservices โดย:
- ป้องกัน Cascade Failure - ไม่ให้ความล้มเหลวลุกลามไปยังระบบอื่น
- Fail Fast - ให้ Error กลับเร็วแทนที่จะรอ Timeout
- Auto Recovery - ลองเชื่อมต่อใหม่อัตโนมัติเมื่อเวลาผ่านไป
- Graceful Degradation - ให้บริการลดทอนแทนการหยุดทำงาน
การใช้ Circuit Breaker อย่างถูกต้องจะทำให้ระบบมีความเสถียรและสามารถรับมือกับความผิดพลาดได้อย่างมีประสิทธิภาพ!