Circuit Breaker & Polly ile Dayanıklılık

Hizmet kesintilerine karşı dayanıklılık sağlamak için circuit breaker, retry, timeout, bulkhead gibi desenlerin Polly ile .NET'te uygulanması gösterilir.

Circuit Breaker & Polly ile Dayanıklılık

Circuit Breaker & Resilience: Polly Kütüphanesi ile Fault-Tolerant Sistemler

Dağıtık sistemlerde, mikroservisler arası ağ çağrıları, veritabanı bağlantıları veya üçüncü parti API'ler her zaman hatasız çalışmaz. Bir servis yavaşlar, zaman aşımına uğrar veya tamamen çökerse, bu durum tüm sistemi zincirleme olarak etkileyebilir (cascade failure). İşte Polly, .NET ekosisteminde bu tür geçici hatalara (transient faults) karşı dayanıklılık (resilience) sağlamak için kullanılan en popüler kütüphanedir. Polly, size Retry (Yeniden Dene), Circuit Breaker (Devre Kesici), Timeout (Zaman Aşımı), Bulkhead (Bölme) ve Fallback (Yedek) gibi desenleri tek bir akıcı (fluent) API ile sunar.


1. Polly Nedir ve Neden Kullanılır?

Polly, .NET için bir geçici hata işleme (transient fault handling) kütüphanesidir. Uygulamanızın, geçici (geçici ağ sorunları, bağlantı zaman aşımı, hizmetin geçici olarak meşgul olması) veya kalıcı (servis çökmesi, yetki hatası) hatalara karşı nasıl davranacağını tanımlamanıza olanak tanır.

  • Retry (Yeniden Dene): Belirli bir hata türü oluştuğunda işlemi belirli sayıda veya sürede tekrar dener.

  • Circuit Breaker (Devre Kesici): Belirli sayıda hata oluştuğunda, belirli bir süre boyunca çağrıları doğrudan başarısız kabul ederek sistemi korur.

  • Timeout (Zaman Aşımı): Bir işlemin belirli bir sürede tamamlanmasını zorunlu kılar, aksi halde iptal eder.

  • Bulkhead (Bölme): Farklı işlem gruplarını birbirinden izole ederek, bir grubun tüm kaynakları tüketmesini engeller.

  • Fallback (Yedek): Hata durumunda varsayılan veya önbelleğe alınmış bir değer döndürür.

  • Cache (Önbellek): Belirli bir süre boyunca aynı parametrelerle yapılan çağrıları önbelleğe alır.


2. Polly ile Temel Desenlerin Uygulanması

Kurulum: dotnet add package Polly

A. Retry (Yeniden Dene)

csharp

using Polly;
using Polly.Retry;

// 3 kez yeniden dene, her denemede bekleme süresi artsın (üstel geri çekilme - exponential backoff)
AsyncRetryPolicy<HttpResponseMessage> retryPolicy = Policy
    .Handle<HttpRequestException>() // HTTP hatalarını yakala
    .Or<TimeoutException>()         // veya zaman aşımı hatalarını
    .WaitAndRetryAsync(
        3, // maksimum deneme sayısı
        retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), // 2, 4, 8 saniye bekle
        onRetry: (outcome, timespan, retryCount, context) =>
        {
            Console.WriteLine($"Retry {retryCount} sonra {timespan} saniye bekleniyor.");
        }
    );

// Kullanım:
var response = await retryPolicy.ExecuteAsync(() => 
    httpClient.GetAsync("https://api.example.com/data")
);

Artıları: Geçici ağ dalgalanmalarında kurtarıcıdır. Üstel geri çekilme, sunucu üzerindeki baskıyı azaltır.

B. Circuit Breaker (Devre Kesici)

csharp

using Polly.CircuitBreaker;

// İleri-Devre Kesici (Advanced Circuit Breaker)
AsyncCircuitBreakerPolicy<HttpResponseMessage> circuitBreakerPolicy = Policy
    .Handle<HttpRequestException>()
    .Or<TimeoutException>()
    .AdvancedCircuitBreakerAsync(
        failureThreshold: 0.5, // %50 hata oranı
        samplingDuration: TimeSpan.FromSeconds(10), // son 10 saniye
        minimumThroughput: 8, // en az 8 istek
        durationOfBreak: TimeSpan.FromSeconds(30) // 30 saniye açık kal
    );

// Kullanım:
try
{
    var response = await circuitBreakerPolicy.ExecuteAsync(() => 
        httpClient.GetAsync("https://api.example.com/data")
    );
}
catch (BrokenCircuitException ex)
{
    // Devre açık, çağrı yapılmadı.
    Console.WriteLine("Servis şu anda kullanılamıyor, lütfen daha sonra tekrar deneyin.");
}
catch (HttpRequestException ex)
{
    // Devre kapalı ama çağrı başarısız oldu.
}

Artıları: Hatalı bir servisi "devre dışı" bırakarak, zincirleme hataları (cascade failure) önler. Kaynakları korur.

C. Timeout (Zaman Aşımı)

csharp

using Polly.Timeout;

AsyncTimeoutPolicy timeoutPolicy = Policy
    .TimeoutAsync(TimeSpan.FromSeconds(5)); // 5 saniye

// Kullanım:
try
{
    var response = await timeoutPolicy.ExecuteAsync(async (ct) => 
        await httpClient.GetAsync("https://api.example.com/data", ct)
    );
}
catch (TimeoutRejectedException ex)
{
    Console.WriteLine("İşlem 5 saniyede tamamlanamadı.");
}

Artıları: Yavaş çağrıların kaynakları tüketmesini engeller.

D. Bulkhead (Bölme - Kaynak İzolasyonu)

csharp

using Polly.Bulkhead;

// Aynı anda en fazla 4 paralel çağrı, kuyrukta en fazla 10 bekleyebilir
AsyncBulkheadPolicy bulkheadPolicy = Policy
    .BulkheadAsync(
        maxParallelization: 4,
        maxQueuingActions: 10
    );

// Kullanım:
await bulkheadPolicy.ExecuteAsync(async () => 
    await httpClient.GetAsync("https://api.example.com/data")
);

Artıları: Bir grup işlemin tüm kaynakları (thread'leri) tüketmesini önler, diğer işlemlerin aç kalmasını engeller.

E. Fallback (Yedek/Yedekleme)

csharp

using Polly.Fallback;

// Hata durumunda önbellekten veya varsayılan değer döndür
AsyncFallbackPolicy<HttpResponseMessage> fallbackPolicy = Policy<HttpResponseMessage>
    .Handle<HttpRequestException>()
    .Or<TimeoutException>()
    .FallbackAsync(
        fallbackValue: new HttpResponseMessage(System.Net.HttpStatusCode.OK) 
        { 
            Content = new StringContent("{\"message\":\"Servis geçici olarak kullanılamıyor. Lütfen daha sonra tekrar deneyin.\"}") 
        },
        onFallbackAsync: (ex, ctx) => 
        {
            Console.WriteLine($"Fallback tetiklendi: {ex.Exception?.Message}");
            return Task.CompletedTask;
        }
    );

Artıları: Hata durumunda kullanıcıya boş yanıt yerine anlamlı bir mesaj veya önbelleğe alınmış veri döndürür.


3. Tüm Desenleri Bir Arada Kullanmak (Policy Wrap)

Polly, birden fazla politikayı zincirleme olarak birleştirmenize olanak tanır. Örneğin, önce Retry, sonra Circuit Breaker, sonra Timeout.

csharp

var retryPolicy = Policy.Handle<HttpRequestException>().WaitAndRetryAsync(3, _ => TimeSpan.FromSeconds(2));
var circuitBreakerPolicy = Policy.Handle<HttpRequestException>().CircuitBreakerAsync(5, TimeSpan.FromSeconds(30));
var timeoutPolicy = Policy.TimeoutAsync(TimeSpan.FromSeconds(5));

// Zincirleme (dıştan içe doğru çalışır: önce timeout, sonra retry, en son circuit breaker)
var combinedPolicy = Policy.WrapAsync(timeoutPolicy, retryPolicy, circuitBreakerPolicy);

// Kullanım:
var response = await combinedPolicy.ExecuteAsync(async (ct) => 
    await httpClient.GetAsync("https://api.example.com/data", ct)
);

Bu sayede, çağrı önce zaman aşımına uğrayıp uğramadığını kontrol eder, sonra yeniden dener, en son devre kesiciye takılır.


4. HTTP Client Factory ile Polly Entegrasyonu

.NET Core'daki IHttpClientFactory, Polly ile harika entegre olur. AddPolicyHandler() ile tüm HttpClient çağrılarına otomatik olarak politika uygulayabilirsiniz.

csharp

builder.Services.AddHttpClient("MyApi", client =>
{
    client.BaseAddress = new Uri("https://api.example.com/");
})
.AddPolicyHandler((services, request) => 
{
    // Dinamik politika: İstek metoduna göre farklı politika uygula
    if (request.Method == HttpMethod.Get)
    {
        return Policy.Handle<HttpRequestException>()
            .WaitAndRetryAsync(3, _ => TimeSpan.FromSeconds(2));
    }
    else // POST, PUT, DELETE
    {
        return Policy.Handle<HttpRequestException>()
            .CircuitBreakerAsync(5, TimeSpan.FromSeconds(30));
    }
})
.AddPolicyHandler(Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(5)));

5. Yaygın Tuzaklar ve Çözümleri

Tuzak Çözüm
Sonsuz Retry (Infinite Retry) Maksimum retry sayısı belirleyin. Retry süresi arttıkça, üstel geri çekilme (exponential backoff) kullanarak servisi koruyun.
Circuit Breaker'ı Çok Hassas Ayarlamak Minimum throughput (en az istek sayısı) ve failure threshold'u (hata oranı) iyi ayarlayın. Çok düşük eşikler, sağlıklı servisleri bile açık hale getirebilir.
Polly'yi Her Yerde Kullanmak (Overusing) Sadece dış kaynaklara yapılan ağ çağrılarında kullanın. In-memory hesaplamalar için gerekli değildir.
Zaman Aşımı ile Retry'i Birleştirmek Zaman aşımı politikanız retry'den daha büyük olmalıdır. Aksi halde retry, zaman aşımından önce bitebilir.
Fallback'in Hata Durumunda Fırlatması Fallback politikasının onFallback delegate'i içinde yeni bir exception fırlatmamaya dikkat edin. Aksi halde fallback anlamsız hale gelir.

Sonuç:

Polly, dağıtık sistemlerde dayanıklılığı (resilience) sağlamanın olmazsa olmazıdır. Retry, Circuit Breaker, Timeout, Bulkhead ve Fallback desenlerini doğru kombinasyonlarla kullanarak, uygulamanızı dış servis hatalarına karşı güçlendirebilirsiniz. İster tek bir HttpClient politikası, ister karmaşık zincirleme politikalar olsun, Polly'nin akıcı API'si size esnek ve okunabilir bir çözüm sunar. Unutmayın: Hataları önleyemezsiniz, ancak onlara nasıl tepki vereceğinizi kontrol edebilirsiniz. Polly, bu tepkiyi kontrol etmenin en güzel yoludur.

Tüm yazılar