Mikroservis Mimarisi & Dayanıklılık 6 dk okuma 2 okunma

Bulkhead Pattern: Kaynak İzolasyonu ile Dayanıklılık

Bulkhead Pattern, bir sistemdeki bir bileşenin arızalanmasının diğerlerini etkilememesi için kaynakları izole eden bir dayanıklılık desenidir. Gemi teknelerindeki bölmelerden (bulkhead) ilham alan bu desen; thread pool izolasyonu, connection pool ayrımı, servis bazlı kaynak sınırlama ve .NET'te Polly Bulkhead ile uygulama stratejileriyle ele alınır.

Bulkhead Pattern: Kaynak İzolasyonu ile Dayanıklılık

Bulkhead Pattern: Kaynak İzolasyonu ile Dayanıklılık

Mikroservis mimarisinde, bir servisin yavaşlaması veya çökmesi, zincirleme bir etkiyle (cascade failure) tüm sistemi çökertebilir. Bir servisteki yavaşlık, thread havuzunu tüketebilir; bir veritabanı bağlantısının tıkanması, diğer servislerin de veritabanına erişememesine yol açabilir. İşte Bulkhead Pattern (Bölme Deseni), bu sorunu çözmek için kaynakları birbirinden izole ederek, bir arızanın diğer bileşenlere yayılmasını engeller.


1. Bulkhead Pattern Nedir?

Bulkhead Pattern, adını gemi mühendisliğinden alır. Bir geminin gövdesi, su geçirmez bölmelere (bulkhead) ayrılmıştır. Bir bölme su alırsa, diğer bölmeler kuru kalır ve gemi batmaz. Yazılım dünyasında bu desen, kaynakları (thread, connection, memory) izole ederek bir bileşendeki arızanın diğerlerine yayılmasını önler.

Temel Fikir: Her servis veya işlem, kendi kaynak havuzuna sahip olmalıdır. Bir havuz tükendiğinde, diğer havuzlar etkilenmez.


2. Bulkhead Türleri

Tür Açıklama Kullanım Alanı
Thread Pool Bulkhead Her servis/operasyon için ayrı thread havuzu Farklı downstream servis çağrıları
Semaphore Bulkhead Eşzamanlı istek sayısını sınırlar (thread oluşturmaz) Hafif, hızlı işlemler
Connection Pool Bulkhead Her servis için ayrı veritabanı bağlantı havuzu Çoklu veritabanı erişimi
Process/Container Bulkhead Her servisi ayrı container'da çalıştırma Mikroservis izolasyonu
Tenant Bulkhead Her kiracı için ayrı kaynak havuzu Multi-tenant SaaS

3. Thread Pool vs. Semaphore Bulkhead

Thread Pool Bulkhead:

  • Her işlem için ayrı bir thread havuzu oluşturur.

  • Yavaş bir servis, kendi thread havuzunu tüketir; diğer servislerin thread'lerini etkilemez.

  • Asenkron işlemler için idealdir.

  • Dezavantaj: Thread oluşturma maliyeti, context switching yükü.

Semaphore Bulkhead:

  • Eşzamanlı istek sayısını bir semaphore ile sınırlar.

  • Thread oluşturmaz; mevcut thread'leri kullanır.

  • Hafif ve hızlıdır.

  • Dezavantaj: Thread havuzunu bloke edebilir (senkron çağrılarda).


4. .NET'te Polly ile Bulkhead Uygulaması

Polly, .NET ekosisteminde Bulkhead Pattern'ı uygulamak için en yaygın kullanılan kütüphanedir.

A. Semaphore Bulkhead (Polly)

csharp

using Polly;
using Polly.Bulkhead;

// Semaphore Bulkhead: Aynı anda en fazla 10 eşzamanlı istek, kuyrukta 20 bekleyebilir
var bulkheadPolicy = Policy.BulkheadAsync(
    maxParallelization: 10,
    maxQueuingActions: 20,
    onBulkheadRejectedAsync: (context) =>
    {
        Console.WriteLine("Bulkhead dolu, istek reddedildi.");
        return Task.CompletedTask;
    });

// Kullanım
try
{
    var response = await bulkheadPolicy.ExecuteAsync(async () =>
    {
        return await _httpClient.GetAsync("https://api.example.com/data");
    });
}
catch (BulkheadRejectedException ex)
{
    // İstek reddedildi - kullanıcıya anlamlı mesaj dön
    return StatusCode(503, "Servis şu anda yoğun, lütfen daha sonra tekrar deneyin.");
}

B. Her Servis İçin Ayrı Bulkhead

csharp

public class ServiceClient
{
    private readonly AsyncBulkheadPolicy _orderServiceBulkhead;
    private readonly AsyncBulkheadPolicy _paymentServiceBulkhead;
    private readonly AsyncBulkheadPolicy _inventoryServiceBulkhead;

    public ServiceClient()
    {
        // Her servis için ayrı bulkhead - biri tükenirse diğerleri etkilenmez
        _orderServiceBulkhead = Policy.BulkheadAsync(10, 20);
        _paymentServiceBulkhead = Policy.BulkheadAsync(5, 10);
        _inventoryServiceBulkhead = Policy.BulkheadAsync(15, 30);
    }

    public async Task<Order> GetOrderAsync(int id)
    {
        return await _orderServiceBulkhead.ExecuteAsync(async () =>
            await _httpClient.GetFromJsonAsync<Order>($"/api/orders/{id}"));
    }

    public async Task<Payment> ProcessPaymentAsync(PaymentRequest request)
    {
        return await _paymentServiceBulkhead.ExecuteAsync(async () =>
            await _httpClient.PostAsJsonAsync("/api/payments", request));
    }
}

C. Polly v8 ile Bulkhead (Resilience Pipeline)

Polly v8, yeni Resilience Pipeline API'si ile daha temiz bir yapılandırma sunar.

csharp

using Polly;
using Polly.Bulkhead;

var pipeline = new ResiliencePipelineBuilder()
    .AddBulkhead(new BulkheadStrategyOptions
    {
        MaxParallelization = 10,
        MaxQueuingActions = 20,
        ShouldHandle = new PredicateBuilder().Handle<HttpRequestException>(),
        OnBulkheadRejected = args =>
        {
            Console.WriteLine("Bulkhead reddetti!");
            return default;
        }
    })
    .AddTimeout(TimeSpan.FromSeconds(5))
    .AddRetry(new RetryStrategyOptions
    {
        MaxRetryAttempts = 3,
        Delay = TimeSpan.FromSeconds(1)
    })
    .Build();

var result = await pipeline.ExecuteAsync(async token =>
{
    return await _httpClient.GetStringAsync("https://api.example.com/data", token);
});

5. HTTP Client Factory ile Bulkhead Entegrasyonu

.NET'in IHttpClientFactory yapısı ile her downstream servis için ayrı HttpClient ve bulkhead tanımlanabilir.

csharp

// Program.cs
builder.Services.AddHttpClient("OrderService", client =>
{
    client.BaseAddress = new Uri("https://orders.example.com");
})
.AddResilienceHandler("order-bulkhead", builder =>
{
    builder.AddBulkhead(new BulkheadStrategyOptions
    {
        MaxParallelization = 20,
        MaxQueuingActions = 50
    });
    builder.AddTimeout(TimeSpan.FromSeconds(10));
});

builder.Services.AddHttpClient("PaymentService", client =>
{
    client.BaseAddress = new Uri("https://payments.example.com");
})
.AddResilienceHandler("payment-bulkhead", builder =>
{
    // Ödeme servisi daha kritik - daha düşük paralellik, daha uzun timeout
    builder.AddBulkhead(new BulkheadStrategyOptions
    {
        MaxParallelization = 5,
        MaxQueuingActions = 10
    });
    builder.AddTimeout(TimeSpan.FromSeconds(30));
});

6. Kubernetes ve Container Seviyesinde Bulkhead

Bulkhead Pattern sadece kod seviyesinde değil, altyapı seviyesinde de uygulanabilir.

A. Kaynak Limitleri (Resource Limits)

Her pod için CPU ve bellek limitleri tanımlayarak, bir pod'un tüm node kaynaklarını tüketmesini engelleyin.

yaml

apiVersion: v1
kind: Pod
metadata:
  name: order-service
spec:
  containers:
  - name: order-service
    image: myregistry/order-service:latest
    resources:
      requests:
        memory: "256Mi"
        cpu: "250m"
      limits:
        memory: "512Mi"
        cpu: "500m"    # Bu pod en fazla 500m CPU kullanabilir

B. Ayrı Node Pool'lar

Kritik servisleri ayrı node pool'larda çalıştırarak fiziksel izolasyon sağlayın.

yaml

# Kritik servisler için ayrı node pool
nodeSelector:
  node-pool: critical-services

C. Network Policy ile İzolasyon

Servisler arası ağ trafiğini kısıtlayarak, bir servisin diğerlerini etkilemesini önleyin.

yaml

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: order-service-policy
spec:
  podSelector:
    matchLabels:
      app: order-service
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: api-gateway

7. Bulkhead vs. Circuit Breaker vs. Rate Limiting

Bu üç desen sıklıkla karıştırılır. Farkları:

Desen Amaç Mekanizma
Bulkhead Kaynak izolasyonu Her servis için ayrı havuz
Circuit Breaker Hatalı servisi geçici olarak devre dışı bırakma Hata eşiği aşıldığında devreyi aç
Rate Limiting Aşırı kullanımı önleme Zaman biriminde istek sayısını sınırla

Birlikte Kullanım: En iyi dayanıklılık stratejisi, üçünü birlikte kullanmaktır.

csharp

var pipeline = new ResiliencePipelineBuilder()
    .AddRateLimiter(new SlidingWindowRateLimiter(...))  // 1. Rate Limiting
    .AddBulkhead(new BulkheadStrategyOptions { ... })    // 2. Bulkhead
    .AddCircuitBreaker(new CircuitBreakerStrategyOptions { ... }) // 3. Circuit Breaker
    .AddRetry(new RetryStrategyOptions { ... })          // 4. Retry
    .AddTimeout(TimeSpan.FromSeconds(10))                // 5. Timeout
    .Build();

8. Multi-Tenant SaaS'da Bulkhead

Çok kiracılı (multi-tenant) SaaS uygulamalarında, bir kiracının aşırı kullanımı diğerlerini etkilememelidir.

csharp

public class TenantBulkheadManager
{
    private readonly ConcurrentDictionary<string, ResiliencePipeline> _pipelines = new();

    public ResiliencePipeline GetPipelineForTenant(string tenantId)
    {
        return _pipelines.GetOrAdd(tenantId, _ =>
        {
            return new ResiliencePipelineBuilder()
                .AddBulkhead(new BulkheadStrategyOptions
                {
                    MaxParallelization = 10,  // Her kiracı en fazla 10 eşzamanlı istek
                    MaxQueuingActions = 20
                })
                .AddTimeout(TimeSpan.FromSeconds(30))
                .Build();
        });
    }
}

// Kullanım
var pipeline = _bulkheadManager.GetPipelineForTenant(tenantId);
var result = await pipeline.ExecuteAsync(async token =>
{
    return await _service.ProcessAsync(token);
});

9. En İyi Pratikler

Pratik Açıklama
Kritiklik Seviyesine Göre Ayırın Kritik servisler (ödeme) için daha küçük, daha korumalı havuzlar.
Her Downstream Servis İçin Ayrı Havuz Bir servisin yavaşlaması diğerlerini etkilemesin.
Doğru Boyutlandırma Havuz boyutunu, servis kapasitesine ve beklenen yüke göre belirleyin.
Reddedilen İstekleri Yönetin BulkheadRejectedException durumunda kullanıcıya anlamlı mesaj dönün (503).
Monitoring ve Alerting Bulkhead reddetme oranlarını izleyin; yüksekse havuz boyutunu gözden geçirin.
Circuit Breaker ile Birleştirin Bulkhead tükendiğinde, circuit breaker ile geçici olarak devreyi açın.
Kubernetes Limitleri Kullanın Container seviyesinde CPU/bellek limitleri ile fiziksel izolasyon sağlayın.
Test Edin Yük testleri ile bulkhead davranışını doğrulayın; reddetme senaryolarını test edin.

10. Gerçek Dünya Senaryosu

Senaryo: Bir e-ticaret uygulamasında, ürün listeleme sayfası üç servisten veri çekiyor: Ürün Servisi, Stok Servisi ve Yorum Servisi.

Problem: Yorum Servisi yavaşladığında, tüm thread havuzu tükeniyor ve ürün listeleme sayfası çöküyor.

Çözüm: Her servis için ayrı bulkhead tanımlayın.

csharp

public class ProductPageComposer
{
    private readonly ResiliencePipeline _productPipeline;
    private readonly ResiliencePipeline _stockPipeline;
    private readonly ResiliencePipeline _reviewPipeline;

    public ProductPageComposer()
    {
        _productPipeline = CreateBulkhead(20, 50);  // Ürün servisi kritik
        _stockPipeline = CreateBulkhead(10, 20);    // Stok servisi orta
        _reviewPipeline = CreateBulkhead(5, 10);    // Yorum servisi kritik değil
    }

    public async Task<ProductPageDto> ComposeAsync(int productId)
    {
        var productTask = _productPipeline.ExecuteAsync(async token =>
            await _productService.GetAsync(productId, token));

        var stockTask = _stockPipeline.ExecuteAsync(async token =>
            await _stockService.GetAsync(productId, token));

        // Yorum servisi başarısız olursa sayfayı bozmasın
        var reviewTask = _reviewPipeline.ExecuteAsync(async token =>
            await _reviewService.GetAsync(productId, token))
            .AsTask()
            .ContinueWith(t => t.IsFaulted ? new List<Review>() : t.Result);

        await Task.WhenAll(productTask, stockTask, reviewTask);

        return new ProductPageDto
        {
            Product = await productTask,
            Stock = await stockTask,
            Reviews = await reviewTask
        };
    }
}

Bu sayede Yorum Servisi yavaşlasa bile, ürün ve stok bilgileri sorunsuz görüntülenir; yorumlar boş döner.


Sonuç

Bulkhead Pattern, mikroservis mimarisinde dayanıklılığın temel yapı taşlarından biridir. Kaynakları izole ederek, bir bileşendeki arızanın tüm sistemi çökertmesini engeller. Thread pool ve semaphore tabanlı bulkhead'ler kod seviyesinde; resource limits ve network policy ise altyapı seviyesinde izolasyon sağlar. Polly, .NET ekosisteminde bulkhead uygulamak için olgun ve güçlü bir kütüphanedir. Circuit Breaker, Rate Limiting ve Retry ile birlikte kullanıldığında, sisteminiz dalgalanmalara ve arızalara karşı çok daha dirençli hale gelir. Unutmayın: Güçlü bir zincir, en zayıf halkası kadar güçlüdür; bulkhead, her halkayı ayrı ayrı güçlendirmenizi sağlar.

Tüm yazılar Soru ya da yorumun mu var? Yaz