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

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

Bulkhead Pattern, bir bileşendeki kaynak tükenmesinin diğerlerine yayılmasını engelleyen bir dayanıklılık desenidir. Gemi bölmelerinden ilham alan bu desen, her servis veya operasyon için ayrı kaynak havuzları oluşturur. Polly, thread pool ve semaphore tabanlı bulkhead'ler ile .NET'te uygulanır; Kubernetes resource limitleri ve network policy'leri ile altyapı seviyesinde desteklenir.

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

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

Mikroservis mimarisinde en sinsi arıza türü, bir servisin çökmesi değil, yavaşlamasıdır. Yavaşlayan bir servis, kendisine ayrılan thread'leri, bağlantıları ve belleği tüketmeye başlar; bu kaynaklar tükendiğinde ise zincirleme bir çöküş (cascade failure) yaşanır. Tüm sistem, tek bir yavaş servis yüzünden kullanılamaz hale gelir. İşte Bulkhead Pattern (Bölme Deseni), bu sorunu çözmek için kaynakları birbirinden izole eder.


1. Kaynak Tükenmesi (Resource Exhaustion) Nedir?

Bir servis yavaşladığında veya çöktüğünde, ona bağlanan tüm istemciler beklemeye başlar. Bu bekleyen istekler, aşağıdaki kaynakları tüketir:

Kaynak Tükenme Sonucu
Thread Pool Yeni istekler işlenemez; tüm thread'ler bekliyor
Connection Pool Veritabanı bağlantısı alınamaz; yeni sorgular çalışamaz
Bellek Bekleyen istekler bellekte birikir; OOM hatası
Socket/Port Yeni bağlantı kurulamaz; ağ hataları

Kritik Nokta: Bu kaynaklar paylaşımlıdır. Bir servis için ayrılan thread'ler tükenirse, diğer servisler de bu thread havuzunu kullanamaz. İşte Bulkhead, bu paylaşımı izole havuzlara bölerek sorunu çözer.


2. 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, her servis veya operasyon için ayrı kaynak havuzu oluşturarak, bir bileşendeki arızanın diğerlerine yayılmasını önler.

Temel Fikir: Her downstream servis çağrısı, kendi thread havuzuna veya bağlantı havuzuna sahip olmalıdır. Bir havuz tükendiğinde, diğer havuzlar etkilenmez.


3. Bulkhead Türleri

Tür Mekanizma Kullanım Alanı
Thread Pool Bulkhead Her operasyon için ayrı thread havuzu Asenkron, I/O-bound işlemler
Semaphore Bulkhead Eşzamanlı istek sayısını sınırlar 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

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 (Temel)

csharp

using Polly;
using Polly.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;
    });

try
{
    var response = await bulkheadPolicy.ExecuteAsync(async () =>
    {
        return await _httpClient.GetAsync("https://api.example.com/data");
    });
}
catch (BulkheadRejectedException ex)
{
    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()
    {
        _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}"));
    }
}

C. Polly v8 ile Resilience Pipeline

csharp

var pipeline = new ResiliencePipelineBuilder()
    .AddBulkhead(new BulkheadStrategyOptions
    {
        MaxParallelization = 10,
        MaxQueuingActions = 20,
        ShouldHandle = new PredicateBuilder().Handle<HttpRequestException>()
    })
    .AddTimeout(TimeSpan.FromSeconds(5))
    .AddRetry(new RetryStrategyOptions
    {
        MaxRetryAttempts = 3,
        Delay = TimeSpan.FromSeconds(1)
    })
    .Build();

5. HTTP Client Factory ile Bulkhead Entegrasyonu

csharp

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 =>
{
    builder.AddBulkhead(new BulkheadStrategyOptions
    {
        MaxParallelization = 5,
        MaxQueuingActions = 10
    });
    builder.AddTimeout(TimeSpan.FromSeconds(30));
});

6. Kubernetes ve Container Seviyesinde Bulkhead

A. Kaynak Limitleri (Resource Limits)

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"

B. Network Policy ile İzolasyon

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

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(...))
    .AddBulkhead(new BulkheadStrategyOptions { ... })
    .AddCircuitBreaker(new CircuitBreakerStrategyOptions { ... })
    .AddRetry(new RetryStrategyOptions { ... })
    .AddTimeout(TimeSpan.FromSeconds(10))
    .Build();

8. Multi-Tenant SaaS'da Bulkhead

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,
                    MaxQueuingActions = 20
                })
                .AddTimeout(TimeSpan.FromSeconds(30))
                .Build();
        });
    }
}

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

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

        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 kaynak tükenmesini önlemenin en etkili yoludur. 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, Retry ve Timeout 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