Skip to content
~/sph.sh

Circuit Breaker Pattern: Zincirleme Hataları Önleyen Dayanıklı Mikroservisler

Dağıtık sistemlerde zincirleme hataları önlemek için gerçek dünyadan Circuit Breaker pattern implementasyonu ve kanıtlanmış stratejiler

Bir payment servisi yavaş fail ettiğinde tüm platformu etkileyebilir. Her request timeout olmak için 30 saniye beklediğinde, 12 farklı serviste trafik sıkışması yaratır. Bu klasik zincirleme hata pattern'idir. Circuit Breaker pattern ile bu problemi nasıl çözdüğümüzü ve bu tür incident'ları çözerken resilience hakkında öğrendiklerimizi paylaşacağım.

Problem: Yavaş Olmak Ölü Olmaktan Kötü

Şunu hayal edin: Payment provider'ınızın API'si yavaş cevap vermeye başlıyor. Down değil, sadece normal 200ms yerine request başına 20-30 saniye alıyor. Servisiniz sadakatle bekliyor. Bu arada gelen request'ler birikiyor. Thread pool'lar tükeniyor. Memory consumption patlıyor. Sonunda sağlıklı servisiniz sağlıksız hale geliyor ve enfeksiyon upstream'e yayılıyor.

Bu pattern tüm platformları öldürebilir. En zorlu kısmı? Monitoring'iniz tüm servislerin "up" olduğunu gösteriyor - sadece cevap vermiyorlar. Health check'ler geçer ama request'ler timeout'a düşer.

Circuit Breaker: Sisteminizin Güvenlik Valfi

Circuit Breaker pattern evinizdeki elektrik sigortası gibi çalışır. İşler ters gittiğinde atar, hasarın yayılmasını önler. Ama evinizdeki sigortadan farklı olarak bu akıllı - problemin düzelip düzelmediğini test edebilir ve otomatik olarak recover edebilir. Opsiyonel half-open state sayesinde servis iyileştiğinde akış yeniden başlar.

Üç State

typescript
enum CircuitState {  CLOSED = 'CLOSED',     // Normal operasyon, request'ler akıyor  OPEN = 'OPEN',         // Circuit atmış, request'ler hemen fail ediyor  HALF_OPEN = 'HALF_OPEN' // Servis recover olmuş mu test ediyor}

Bir kulüpteki bouncer gibi düşünün:

  • CLOSED: "Gelin, her şey yolunda"
  • OPEN: "Kimse giremiyor, içeride problem var"
  • HALF_OPEN: "Bir kişiyle kontrol edeyim güvenli mi"

Gerçek Implementasyon: Gerçekten İşe Yarayan

İşte bu zorlukları ele alan bir circuit breaker implementasyonu. Bu pattern yüksek request hacmi olan servislerde güvenilir olduğunu kanıtlamıştır:

typescript
interface CircuitBreakerConfig {  failureThreshold: number;      // Açılmadan önceki failure sayısı  successThreshold: number;       // Half-open'dan kapanmak için success sayısı  timeout: number;               // Request timeout ms cinsinden  resetTimeout: number;          // Half-open denemeden önce bekleme  volumeThreshold: number;       // Değerlendirmeden önce min request  errorThresholdPercentage: number; // Trip için error yüzdesi}
class CircuitBreaker<T> {  private state: CircuitState = CircuitState.CLOSED;  private failureCount = 0;  private successCount = 0;  private lastFailureTime?: Date;  private requestCount = 0;  private errorCount = 0;  private window = new RollingWindow(10000); // 10 saniyelik window
  constructor(    private readonly config: CircuitBreakerConfig,    private readonly protectedFunction: () => Promise<T>  ) {}
  async execute(): Promise<T> {    // Half-open denemeli miyiz kontrol et    if (this.state === CircuitState.OPEN) {      if (this.shouldAttemptReset()) {        this.state = CircuitState.HALF_OPEN;      } else {        throw new CircuitOpenError('Circuit breaker OPEN');      }    }
    try {      const result = await this.executeWithTimeout();      this.onSuccess();      return result;    } catch (error) {      this.onFailure();      throw error;    }  }
  private async executeWithTimeout(): Promise<T> {    return Promise.race([      this.protectedFunction(),      new Promise<T>((_, reject) =>        setTimeout(() => reject(new TimeoutError()), this.config.timeout)      )    ]);  }
  private onSuccess(): void {    this.failureCount = 0;    this.window.recordSuccess();
    if (this.state === CircuitState.HALF_OPEN) {      this.successCount++;      if (this.successCount >= this.config.successThreshold) {        this.state = CircuitState.CLOSED;        this.successCount = 0;      }    }  }
  private onFailure(): void {    this.failureCount++;    this.lastFailureTime = new Date();    this.window.recordFailure();
    if (this.state === CircuitState.HALF_OPEN) {      this.state = CircuitState.OPEN;      this.successCount = 0;      return;    }
    // Hem absolute hem percentage threshold'ları kontrol et    const stats = this.window.getStats();    if (stats.totalRequests >= this.config.volumeThreshold) {      const errorRate = (stats.failures / stats.totalRequests) * 100;      if (errorRate >= this.config.errorThresholdPercentage ||          this.failureCount >= this.config.failureThreshold) {        this.state = CircuitState.OPEN;      }    }  }
  private shouldAttemptReset(): boolean {    return this.lastFailureTime &&      Date.now() - this.lastFailureTime.getTime() >= this.config.resetTimeout;  }}

Production'dan Dersler: Tutorial'ların Söylemediği Şeyler

1. Timeout En Önemli Setting'iniz

Incident pattern analizleri gösteriyor ki çoğu hata (yaklaşık %70) complete failure'lardan değil yavaş response'lardan kaynaklanıyor. Timeout'unuzu agresif ayarlamak yardımcı oluyor:

typescript
const config = {  timeout: 3000,  // 3 saniye - P99'umuz 1.2s, bu problemleri yakalıyor  // NOT 30000!   // Bu bizi öldürdü. 30s beklemek = thread exhaustion};

Bir payment servisinden örnek timing:

  • Normal P50: 180ms
  • Normal P99: 1.2s
  • Circuit breaker timeout: 3s
  • Sonuç: Zincirleme hatalarda önemli azalma

2. Half-Open State Tuzağı

Başlarda half-open'a geçer, bir request gönderir, başarılı olur, circuit'i kapatır, sonra full traffic ile hemen tekrar fail ederdik. Çözüm: kapanmadan önce birden fazla success iste.

typescript
// Bunu yapmaif (testRequest.succeeded) {  this.state = CircuitState.CLOSED; // Boom! Full traffic geri geliyor}
// Bunun yerine bunu yapif (++this.successCount >= this.config.successThreshold) {  this.state = CircuitState.CLOSED; // Kademeli recovery}

3. Retry Logic ile Kombine Et (Ama Dikkatli)

Circuit breaker'lar ve retry'lar feedback loop yaratabilir. Güvenilir bir kombinasyon:

typescript
class ResilientClient {  private circuitBreaker: CircuitBreaker<any>;
  async callWithResilience(request: Request): Promise<Response> {    // Circuit breaker retry logic'i wrap eder, tersi değil    return this.circuitBreaker.execute(async () => {      return await this.retryWithBackoff(request, {        maxAttempts: 3,        backoffMs: [100, 200, 400],        shouldRetry: (error) => {          // Circuit breaker error'larını retry etme          if (error instanceof CircuitOpenError) return false;          // Client error'larını retry etme          if (error.statusCode >= 400 && error.statusCode < 500) return false;          return true;        }      });    });  }}

4. Doğru Metrik'leri Monitor Et

Neyi track etmeli (önem sırasına göre):

  1. Circuit state değişiklikleri - OPEN'da hemen alert
  2. Reset attempt sonuçları - Failed reset'ler = devam eden problem
  3. Request rejection rate - Business impact metriği
  4. OPEN state'te geçen süre - Reset timeout'u ayarlamaya yardımcı

CloudWatch dashboard'umuz:

typescript
// Push ettiğimiz custom metriklerawait cloudwatch.putMetricData({  Namespace: 'CircuitBreakers',  MetricData: [    {      MetricName: 'StateChange',      Value: 1,      Unit: 'Count',      Dimensions: [        { Name: 'ServiceName', Value: this.serviceName },        { Name: 'FromState', Value: oldState },        { Name: 'ToState', Value: newState }      ]    },    {      MetricName: 'RejectedRequests',      Value: rejectedCount,      Unit: 'Count',      Dimensions: [{ Name: 'ServiceName', Value: this.serviceName }]    }  ]});

İleri Seviye Pattern'ler: Basic Circuit Breaking'in Ötesinde

Bulkheading: İzole Circuit Breaker'lar

Tüm servis için tek circuit breaker kullanma. Kritik path'leri izole et:

typescript
class PaymentService {  private readonly chargeBreaker = new CircuitBreaker(chargeConfig);  private readonly refundBreaker = new CircuitBreaker(refundConfig);  private readonly queryBreaker = new CircuitBreaker(queryConfig);
  async chargeCard(request: ChargeRequest): Promise<ChargeResponse> {    // Charge failure'ları refund'ları etkilemiyor    return this.chargeBreaker.execute(() => this.api.charge(request));  }
  async refundPayment(request: RefundRequest): Promise<RefundResponse> {    // Charge'lar fail ederken bile refund'lar çalışıyor    return this.refundBreaker.execute(() => this.api.refund(request));  }}

Bu pattern yoğun trafik dönemlerinde bir endpoint overwhelm olduğunda diğerlerinin çalışmaya devam etmesini sağlar.

Fallback Stratejileri

Her failure eşit değil. Bazen graceful degrade edebilirsin:

typescript
async getProductRecommendations(userId: string): Promise<Product[]> {  try {    return await this.recommendationBreaker.execute(      () => this.mlService.getRecommendations(userId)    );  } catch (error) {    if (error instanceof CircuitOpenError) {      // Basit popularity-based recommendation'lara fallback      return this.getPopularProducts();    }    throw error;  }}

Circuit Breaker Inheritance

Mikroservisler başka mikroservisleri çağırırken, circuit state'i inherit et:

typescript
// API Gatewayif (paymentServiceBreaker.state === CircuitState.OPEN) {  // Payment'a bağımlı order service'i çağırmayı deneme bile  return { error: 'Payment service unavailable', status: 503 };}

Gerçek Dünya Konfigürasyon Örnekleri

Production'da farklı servis tipleri için gerçekten işe yarayan:

typescript
// External API (payment provider'lar, third-party servisler)const externalAPIConfig: CircuitBreakerConfig = {  failureThreshold: 5,           // 5 ardışık failure  successThreshold: 2,           // Recovery için 2 success  timeout: 5000,                // 5 saniye timeout  resetTimeout: 30000,          // 30s sonra recovery dene  volumeThreshold: 10,          // Minimum 10 request gerekli  errorThresholdPercentage: 50  // 50% error rate trip eder};
// Internal mikroservisconst internalServiceConfig: CircuitBreakerConfig = {  failureThreshold: 10,          // Daha toleranslı  successThreshold: 3,  timeout: 3000,                // Daha hızlı timeout  resetTimeout: 10000,          // Daha hızlı recovery attempt'leri  volumeThreshold: 20,  errorThresholdPercentage: 30  // Error rate'lere daha hassas};
// Database connection'larıconst databaseConfig: CircuitBreakerConfig = {  failureThreshold: 3,           // Hızlı trip  successThreshold: 5,           // Yavaş recover  timeout: 1000,                // Çok hızlı timeout  resetTimeout: 5000,           // Hızlı retry  volumeThreshold: 5,  errorThresholdPercentage: 20  // Çok hassas};

Circuit Breaker'ları Test Etmek: Chaos Engineering

Test etmediğin circuit breaker'a güvenemezsin. Chaos testing yaklaşımımız:

typescript
describe('Circuit Breaker Chaos Testleri', () => {  it('kademeli degradation'ı handle etmeli', async () => {    const scenarios = [      { latency: 100, errorRate: 0 },    // Normal      { latency: 500, errorRate: 0.1 },  // Hafif degradation      { latency: 2000, errorRate: 0.3 }, // Büyük degradation      { latency: 5000, errorRate: 0.7 }, // Neredeyse failure    ];
    for (const scenario of scenarios) {      mockService.setScenario(scenario);      await runLoadTest(1000); // 1000 request
      const metrics = await breaker.getMetrics();      if (scenario.errorRate > 0.5) {        expect(breaker.state).toBe(CircuitState.OPEN);      }    }  });});

Production'da AWS Fault Injection Simulator kullanarak random failure'lar inject edip circuit breaker'larımızın doğru respond ettiğini verify ediyoruz.

Bize Pahalıya Mal Olan Hatalar

Hata 1: Sadece Client-Side Circuit Breaking

Başta circuit breaker'ları sadece client'larda implement ettik. Server'ın kendisi problem yaşadığında kendini koruyamıyordu:

typescript
// Kötü: Client kendini koruyor ama server hala overwhelmclass Client {  private breaker = new CircuitBreaker();  async call() { return this.breaker.execute(() => fetch('/api')); }}
// İyi: Server da kendini koruyorclass Server {  private downstreamBreaker = new CircuitBreaker();  async handleRequest(req, res) {    try {      const data = await this.downstreamBreaker.execute(() =>        this.database.query(req.query)      );      res.json(data);    } catch (error) {      if (error instanceof CircuitOpenError) {        res.status(503).json({ error: 'Servis geçici olarak kullanılamıyor' });      }    }  }}

Hata 2: İlgisiz Operasyonlar İçin Circuit Breaker Paylaşmak

"Database operasyonları" için tek circuit breaker'ımız vardı. Write'lar fail ettiğinde read'ler de bloklanıyordu:

typescript
// Kötü: Her şey için tek breakerclass UserService {  private dbBreaker = new CircuitBreaker();
  async getUser(id) {    return this.dbBreaker.execute(() => db.query('SELECT...'));  }
  async createUser(data) {    return this.dbBreaker.execute(() => db.query('INSERT...'));  }}
// İyi: Farklı operasyonlar için ayrı breaker'larclass UserService {  private readBreaker = new CircuitBreaker(readConfig);  private writeBreaker = new CircuitBreaker(writeConfig);
  async getUser(id) {    return this.readBreaker.execute(() => db.query('SELECT...'));  }
  async createUser(data) {    return this.writeBreaker.execute(() => db.query('INSERT...'));  }}

Hata 3: Business Impact'i Düşünmemek

Tüm servislere eşit davrandık. Sonra metrics collection'ı geçirirken payment processing'i blokadık. O dersi hızlı öğrendik.

Implementasyon Checklist'i

Circuit breaker'ları implement ederken kullanışlı bir checklist:

  • Timeout'u P99 latency'nin 2-3 katına ayarla
  • Half-open'dan kapanmadan önce birden fazla success iste
  • Read/write operasyonları için ayrı breaker'lar implement et
  • Business-critical path'ler için fallback davranışı ekle
  • State değişiklikleri ve rejection'lar için metrik export et
  • Production'dan önce chaos engineering ile test et
  • Timeout ve threshold seçimlerini dokümante et
  • Individual failure'larda değil circuit OPEN'da alert et
  • Konfigürasyonda business priority'yi düşün
  • Instant değil gradual recovery implement et

Son Düşünceler: Hızlı Fail Etmek Hakkında

Önemli bir kavrayış: bazen bir servisin yapabileceği en iyi şey hemen fail etmek. 10ms'de 503 response, 30 saniye sonra timeout'tan çok daha iyidir. Kullanıcılar hızlı retry edebilir, sistem recover olabilir. Thread exhaustion çok daha ciddi problemlere yol açar.

Circuit breaker'lar failure'ları önlemekle ilgili değil - failure'ların yayılmasını önlemekle ilgili. Problem düzeldiğinde gerçekten recover edebilecek kadar sistem sağlığını korumakla ilgili.

Circuit breaker'ları problemi yaşamadan önce implement etmek kriz yönetimini çok daha kolay hale getirir.

İlgili Yazılar