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 saha denenmiş stratejiler

Geçen ay payment servisimiz tüm platformu 47 dakika boyunca çökertti. Tamamen fail ettiği için değil - o yönetilebilirdi. Yavaş fail etti. Her request timeout olmak için 30 saniye bekledi, 12 farklı serviste trafik sıkışması yarattı. Klasik zincirleme hata. Circuit Breaker pattern ile bunu nasıl çözdüğümüzü ve sabah 3'te çok fazla distributed sistem debug ettikten sonra resilience hakkında ne öğrendiğimi anlatacağı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'in tüm platformları öldürdüğünü gördüm. En kötü kısmı? Monitoring'iniz tüm servislerin "up" olduğunu gösteriyor - sadece cevap vermiyorlar.

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.

Üç 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 incident'tan sonra yaptığımız circuit breaker. Günde 2M request handle eden 40+ serviste test edildi:

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#

6 aylık incident'ları analiz ettikten sonra, 73%'ü complete failure'lardan değil yavaş response'lardan kaynaklanıyordu. Timeout'unuzu agresif ayarlayın:

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
};

Payment servisimizden gerçek sayılar:

  • Normal P50: 180ms
  • Normal P99: 1.2s
  • Circuit breaker timeout: 3s
  • Sonuç: Zincirleme hatalarda 94% 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 yapma
if (testRequest.succeeded) {
  this.state = CircuitState.CLOSED; // Boom! Full traffic geri geliyor
}

// Bunun yerine bunu yap
if (++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. Test edilmiş kombinasyonumuz:

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 &lt;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 metrikler
await 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 Black Friday'de charge endpoint'imiz overwhelm olduğunda ama refund'lar (müşteri hizmetleri için kritik) çalışmaya devam ettiğinde bizi kurtardı.

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 Gateway
if (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 mikroservis
const 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 overwhelm
class Client {
  private breaker = new CircuitBreaker();
  async call() { return this.breaker.execute(() => fetch('/api')); }
}

// İyi: Server da kendini koruyor
class 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 breaker
class 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'lar
class 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#

40+ serviste circuit breaker implement ettikten sonra checklist'im:

  • 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#

En zor ders? Bazen servisinin yapabileceği en iyi şey hemen fail etmek. 10ms'de o 503 response, 30 saniye sonra timeout'tan sonsuz kat daha iyi. Kullanıcıların retry edebilir. Sistemin recover edebilir. Ama thread exhaustion? O sabah 3'te uyanmak için tek yönlü bilet.

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.

İhtiyacın olmadan önce implement et. Bu konuda bana güven.

Loading...

Yorumlar (0)

Sohbete katıl

Düşüncelerini paylaşmak ve toplulukla etkileşim kurmak için giriş yap

Henüz yorum yok

Bu yazı hakkında ilk düşüncelerini paylaşan sen ol!

Related Posts