Circuit Breaker Pattern Nedir?

Dağıtık (distributed) sistemlerde en çok kullanılan patternlerden biridir. Geliştirdiğimiz uygulamalar artık tek bir sunucuda çalışan monolith uygulamalar değil, onlarca servisten, api ’den ve external kaynaklardan oluşuyor. Birbirine entegre çalışan böyle sistemlerde bir servisten bile sonuç alınamaması ciddi sorunlara yol açabilir ve tüm sistemimizi etkileyebilir. Bu problemin önüne geçmek için projelerimizde Circuit Breaker (Devre Kesici) tasarım desenini kullanabiliriz.

Neden bu pattern i kullanmalıyım?

  • Bir mikro servis ya da external api yavaş çalışıyor veya hiç cevap vermiyorsa, atılan her request sistem kaynaklarını boşa tüketecek ve cloud sistemlerde çalıştığımızı düşünürsek doğrudan maliyetlerimize de etki edecektir.
  • Genel sistem performansına olumlu etkileri vardır.
  • Circuit Breaker, belirli bir hata oranı aşıldığında ilgili servise yeni requestlerin atılmasını engeller.

Circuit Breaker ‘ın 3 state ‘i vardır:

Closed (Kapalı): Sistemin normal olduğu ve requestlerin servise gönderilebildiği durumdur.
Open (Açık) : Hata oranı belirlenen eşiği aşmıştır. Artık yeni istekler gönderilmez, hemen hata döner.
Half-Open (Yarı Açık): Belirli bir süre bekledikten sonra test amaçlı birkaç istek gönderilir. Eğer bunlar başarılı olursa devre tekrar kapalı hale gelir.

Aşağıdaki grafikte her durum için sayaç işlemlerini görebilirsiniz.

circuit-breaker-diagram

.Net Core ile Polly Kullanarak Circuit Breaker İmplementasyonu

Bir web uygulaması açalım ve örnek bir implementasyon yapalım. Polly ve Microsoft.Extensions.Http.Polly paketlerini Nuget ten yükleyelim.

Appsettings.json ‘daki ayarlarımız:

JSON
"CircuitBreakerSettings": {
  "ExceptionsAllowedBeforeBreaking": 5,
  "DurationOfBreakInSeconds": 30
}

Daha sonra Program.cs de implementasyonumuzu aşağıdaki gibi yapabiliriz:

C#
var circuitBreakerSettings = new CircuitBreakerSettings();
builder.Configuration.GetSection(nameof(CircuitBreakerSettings)).Bind(circuitBreakerSettings);

// Circuit Breaker policy oluştur
var circuitBreakerPolicy = Policy
    .Handle<HttpRequestException>()
    .OrResult<HttpResponseMessage>(msg => !msg.IsSuccessStatusCode)
    .CircuitBreakerAsync(
        handledEventsAllowedBeforeBreaking: circuitBreakerSettings.ExceptionsAllowedBeforeBreaking,
        durationOfBreak: TimeSpan.FromSeconds(circuitBreakerSettings.DurationOfBreakInSeconds),
        onBreak: (outcome, breakDelay) =>
        {
            if (outcome.Exception != null)
            {
                Debug.WriteLine($"Circuit breaker açıldı: {outcome.Exception.Message}. Yeni istek için bekleme süresi: {breakDelay}");
            }
            else
            {
                Debug.WriteLine($"Circuit breaker açıldı. StatusCode: {outcome.Result.StatusCode}. Yeni istek için bekleme süresi: {breakDelay}");
            }
        },
        onReset: () =>
        {
            Debug.WriteLine("Circuit breaker kapandı.");
        },
        onHalfOpen: () =>
        {
            Debug.WriteLine("Circuit breaker yarı açık. Dış servise erişim test ediliyor...");
        }
    );

// HTTP client'a policy handler ekle
builder.Services.AddHttpClient<ITestApiClient, TestApiClient>().AddPolicyHandler(circuitBreakerPolicy);

Sadece bu kısmı özetleyecek olursak:

Ayarlarımızı CircuitBreakerSettings class ına bind ediyoruz. Bu örnekte 5 başarısız istekten sonra 30 saniye isteklere ara verecek şekilde konfigürasyonumuzu yaptık. Oluşturduğumuz policy sayesinde HttpRequestException ile fırlatılan hatalar ve başarısız HTTP response lar yakalanıyor, 5 hatadan sonra devre açılıyor (onBreak). Devre kapandığında (onReset) veya yarı açık hale geldiğinde (onHalfOpen) action larımız çalışıyor. Son olarak oluşturduğumuz policy ‘i HttpClient ’a doğrudan bağlıyoruz.

Basit bir request içeren ITestApiClient.cs ve TestApiClient.cs class larımız:

C#
public interface ITestApiClient
{
    Task<string> GetDataAsync();
}
C#
public class TestApiClient : ITestApiClient
{
    private readonly HttpClient _httpClient;

    public TestApiClient(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    public async Task<string> GetDataAsync()
    {
        var response = await _httpClient.GetAsync("https://dummyjson.com/products/333"); // Hata oluşması için olmayan bir ürün Id verdim

        response.EnsureSuccessStatusCode(); // Başarısız durumlarda exception fırlatır ve Circuit Breaker devreye girer

        return await response.Content.ReadAsStringAsync();
    }
}

Son olarak HomeController.cs:

C#
public class HomeController : Controller
{
    private readonly ITestApiClient _apiClient;

    public HomeController(ITestApiClient apiClient)
    {
        _apiClient = apiClient;
    }

    [HttpGet("test")]
    public async Task<IActionResult> Test()
    {
        var data = await _apiClient.GetDataAsync();

        return Ok(data);
    }
}

5 istekten sonra Circuit Breaker devreye girecek şekilde yapılandırmamızı tamamladıktan sonra sonuç :

Circuit Breaker Pattern ‘i ile ilgili anlatacaklarım bu kadar. Pattern ‘in anlaşılabilir olması için kurguyu basit tutmaya çalıştım. Umarım faydalı olmuştur.

Sonraki yazılarda görüşmek dileğiyle…

Faydalandığım linkler:

https://learn.microsoft.com/en-us/azure/architecture/patterns/circuit-breaker

https://medium.com/@90mandalchandan/implementing-circuit-breaker-in-net-core-with-polly-32c62a9984e4

Leave a Comment

Comments

No comments yet. Why don’t you start the discussion?

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir