Dağıtık Sistemler & Eşzamanlılık Yönetimi 7 dk okuma 3 okunma

Distributed Locking: Redis Redlock ve ZooKeeper

Dağıtık sistemlerde birden fazla sürecin paylaşılan bir kaynağa erişimini koordine etmek için kullanılan distributed locking (dağıtık kilitleme) deseni; Redis Redlock algoritması, ZooKeeper'ın geçici sıralı düğüm tabanlı kilitleme mekanizması, iki yaklaşımın güvenlik ve performans karşılaştırması, fencing token ile çit sorunu çözümü ve .NET'te DistributedLock kütüphanesi ele alınır.

Distributed Locking: Redis Redlock ve ZooKeeper

Distributed Locking: Redis Redlock ve ZooKeeper

Dağıtık sistemlerde, birden fazla uygulama instance'ı aynı kaynağa erişmeye çalıştığında, bu kaynağın değerinin bozulmasını veya tutarsız hale gelmesini engellemek gerekir. Tek bir JVM içinde synchronized veya ReentrantLock yeterli olurken, servis birden fazla sunucuya yayıldığında bu kilitler işe yaramaz. İşte Distributed Locking (Dağıtık Kilitleme), bu sorunu çözmek için paylaşılan bir harici depo (Redis, ZooKeeper, veritabanı) üzerinden mutex (karşılıklı dışlama) sağlar .


1. Dağıtık Kilitleme Neden Gerekli?

Kullanım Senaryoları:

  • Zamanlanmış bir görevin (cron job) 3 instance'ta birden tetiklenmesi, ancak yalnızca birinin çalışması gerektiği durumlar .

  • Birden fazla consumer'ın aynı kaynağı işlemeye çalışması; aynı kaynağın iki kez işlenmesinin engellenmesi .

  • Grayscale dağıtım veya ana-yedek (master-slave) geçişlerinde geçici bir "lider" seçilmesi .

  • E-ticaret stok yönetiminde aynı anda gelen siparişlerin aşırı satışa (oversell) yol açmaması .

Temel Gereksinimler:

  • Karşılıklı Dışlama (Mutual Exclusion): Aynı anda yalnızca bir istemci kilidi tutabilmeli .

  • Kilitlenme Koruması (Deadlock Freedom): Kilit tutan istemci çökse bile kilit bir süre sonra serbest bırakılmalı .

  • Hata Toleransı (Fault Tolerance): Çoğu düğüm çalıştığı sürece istemciler kilit alıp bırakabilmeli .


2. Redis Redlock Algoritması

A. Tek Instance Kilidin Sorunu

Basit bir Redis kilidi (SET resource NX PX 30000) tek bir Redis instance'ında çalışır. Ancak Redis ana-yedek (master-replica) mimarisinde şu yarış durumu (race condition) oluşur :

  1. İstemci A, ana düğümde kilidi alır.

  2. Ana düğüm, yazma işlemini yedeğe çoğaltmadan çöker.

  3. Yedek düğüm ana düğüm olarak terfi eder.

  4. İstemci B, aynı kilit için başarılı bir şekilde SET NX yapar.

Sonuç: İki istemci de aynı kilidi tuttuğunu düşünür. Bu, karşılıklı dışlama garantisini ihlal eder .

B. Redlock Çözümü

Redlock, N adet bağımsız Redis ana düğümü (tipik olarak 5) kullanır. Bu düğümler birbirleriyle replikasyon yapmaz. Kilit, ancak istemci düğümlerin çoğunluğunda (N/2 + 1, yani 5 düğümden en az 3'ünde) kilidi alabildiğinde geçerli sayılır .

Kilit Alma Adımları:

  1. İstemci mevcut zamanı milisaniye cinsinden alır (T1) .

  2. Tüm N instance'a sırayla veya paralel olarak kilit isteği gönderir:

    text

    SET resource_name <random_value> NX PX 30000

    Her instance için kısa bir timeout (5-50 ms) kullanılır. random_value benzersiz olmalıdır .

  3. Zaman tekrar alınır (T2) ve geçen süre hesaplanır: elapsed = T2 - T1 .

  4. Kilit şu koşullarda başarılı sayılır:

    • En az N/2 + 1 instance'da kilit alınmış olmalı.

    • elapsed < TTL (kilit geçerlilik süresi) .

  5. Kilit alındıysa, gerçek geçerlilik süresi:

    text

    validity_time = TTL - elapsed - clock_drift_allowance
  6. Kilit alınamadıysa (çoğunluk sağlanamadı veya süre aşıldı), tüm instance'larda kilit derhal serbest bırakılır .

Kilit Bırakma:
Lua script ile yalnızca kendi random_value'si eşleşiyorsa silinir:

lua

if redis.call("get", KEYS[1]) == ARGV[1] then
    return redis.call("del", KEYS[1])
else
    return 0
end

Bu, başka bir istemcinin kilidini yanlışlıkla silmeyi engeller .

C. Redlock'un Sınırları

  • Saat Kayması (Clock Drift): Redlock, yerel saatlerin yaklaşık aynı hızda ilerlediğini varsayar. Ciddi saat kaymaları güvenliği riske atar .

  • Fencing Sorunu: İstemci A kilidi alır, sonra GC duraklaması yaşar, kilit süresi dolar, İstemci B kilidi alır, sonra İstemci A devam eder. İki istemci de aynı kaynağa erişir .

  • Martin Kleppmann'in Eleştirisi: Redlock'in saat varsayımlarına dayandığını ve fencing token olmadan güvenli olmadığını savunur .


3. ZooKeeper Tabanlı Dağıtık Kilit

ZooKeeper, veri modeli dosya sistemine benzeyen bir koordinasyon servisidir. Dağıtık kilit için geçici sıralı düğümler (ephemeral sequential znodes) ve Watcher mekanizması kullanılır .

A. Çalışma Prensibi

  1. Tüm istemciler /locks/job-lock/ dizini altında geçici sıralı düğüm oluşturur: lock-0000000012 .

  2. Her istemci, dizindeki tüm alt düğümleri okur ve sıralar .

  3. Kendi düğümü en küçük sıraya sahipse, kilidi almıştır .

  4. Değilse, yalnızca kendinden bir önceki düğümü dinler (izler) .

  5. Önceki düğüm silindiğinde, istemci tekrar kontrol eder ve en küçük olup olmadığını görür .

  6. Kilit bırakıldığında, istemci kendi düğümünü siler; istemci çökerse, oturum süresi dolunca düğüm otomatik silinir .

Kritik Tasarım Noktaları:

  • Sıralı Düğümler: Doğal bir kuyruk oluşturur; herkes aynı düğüm için yarışmaz .

  • Önceki Düğümü Dinleme: Tüm istemcilerin ebeveyn dizini dinlemesi "sürü etkisi" (herd effect) yaratır. Yalnızca bir önceki düğümü dinlemek, her düğüm değişiminde yalnızca bir istemcinin uyanmasını sağlar .

  • Geçici Düğümler: İstemci çöktüğünde otomatik temizlik sağlar; deadlock riskini azaltır .

B. ZooKeeper'ın Avantajları

  • CP Sistemi (Tutarlılık + Bölünme Toleransı): Yazma işlemleri çoğunluk düğüme senkronize edilmeden onaylanmaz. Lider çöktüğünde, en güncel veriye sahip düğüm lider seçilir (ZAB protokolü) .

  • Kilit Sahibinin Çökmesi Durumunda Anında Temizlik: Geçici düğüm, oturum süresi dolunca otomatik silinir; Redis'teki gibi TTL süresini beklemek gerekmez .

  • Adil Kilit (Fair Lock): Sıralı düğümler sayesinde istemciler talepleri sırasına göre kilidi alır .

C. ZooKeeper'ın Sınırları

  • Performans: Yazma işlemleri çoğunluk düğüme senkronize edildiği için Redis'e göre daha yüksek gecikme ve daha düşük QPS .

  • GC ve Ağ Gecikmesi: İstemci GC duraklaması veya ağ bölünmesi yaşarsa, oturum süresi dolabilir ve kilit kaybedilebilir; ancak istemci hala kilidi tuttuğunu düşünebilir .

  • Sürü Etkisi Riskleri: Yanlış tasarım (tüm istemcilerin aynı düğümü dinlemesi) durumunda performans sorunları .


4. Redlock vs. ZooKeeper: Karşılaştırma

Kriter Redis Redlock ZooKeeper
CAP Modeli AP (Yüksek kullanılabilirlik) CP (Güçlü tutarlılık)
Performans (QPS) Çok yüksek (10万+) Orta-yüksek (çoğunluk yazma nedeniyle düşük)
Kilit Bırakma TTL ile otomatik (gecikmeli) Oturum sonlanınca anında (geçici düğüm)
Adillik (Fairness) Genelde adil değil (Redisson ile opsiyonel) Doğal adil (sıralı düğümler)
Kilit Türleri Mutex, ReadWriteLock, Semaphore (Redisson) Mutex, ReadWriteLock, Semaphore, MultiLock (Curator)
.NET Desteği StackExchange.Redis, RedLock.net, DistributedLock Apache Curator (Java); .NET için DistributedLock
En Uygun Senaryo Yüksek performans, hafif tutarlılık gereksinimleri Güçlü tutarlılık, koordinasyon görevleri

5. Fencing Token ile Çit Sorunu Çözümü

Tüm otomatik süreli kilitlerde görülen çit sorunu (fencing problem), kilit süresi dolduktan sonra eski sahibinin hala kaynağa erişmesi durumudur .

Çözüm: Fencing Token (Monotonik Artan Token)

  • Kilit alındığında, monoton olarak artan bir token (ör. 33, 34, 35...) alınır .

  • Kaynağa erişirken bu token da gönderilir.

  • Kaynak, kendisine gelen en yüksek token'ı saklar ve daha düşük token'lı istekleri reddeder .

csharp

// Fencing token örneği (kavramsal)
var lockHandle = await distributedLock.AcquireAsync("resource");
var fencingToken = lockHandle.FencingToken; // Monotonik artan

await resource.WriteAsync(data, fencingToken);
// Kaynak, fencingToken < sonGorulenToken ise reddeder

6. .NET'te Dağıtık Kilit: DistributedLock Kütüphanesi

DistributedLock (madelson/DistributedLock), .NET ekosisteminde Redis, ZooKeeper, SQL Server, PostgreSQL ve dosya sistemi gibi farklı arka uçları destekleyen olgun bir kütüphanedir .

Kurulum:

bash

dotnet add package DistributedLock
dotnet add package DistributedLock.Redis
dotnet add package DistributedLock.ZooKeeper

Redis ile Kullanım:

csharp

using DistributedLock;
using StackExchange.Redis;

var connection = ConnectionMultiplexer.Connect("localhost:6379");
var lockProvider = new RedisDistributedLockProvider(connection.GetDatabase());

// Acquire (bloklar)
await using (var handle = await lockProvider.AcquireLockAsync("my-resource-key"))
{
    // Kritik bölge - yalnızca bir istemci burada olabilir
    await ProcessOrderAsync();
}

ZooKeeper ile Kullanım:

csharp

using DistributedLock;
using DistributedLock.ZooKeeper;

var lockProvider = new ZooKeeperDistributedLockProvider("localhost:2181");

await using (var handle = await lockProvider.AcquireLockAsync("my-resource-key"))
{
    // Kritik bölge
    await ProcessOrderAsync();
}

TryAcquire (Bloklamadan Deneme):

csharp

using (var handle = await lockProvider.TryAcquireLockAsync("my-resource-key"))
{
    if (handle != null)
    {
        // Kilit alındı
        await ProcessOrderAsync();
    }
    else
    {
        // Kilit başkasında - atla veya bekle
        _logger.LogInformation("Kilit alınamadı, başka bir instance işliyor.");
    }
}

ABP Framework ile Entegrasyon:

csharp

// ABP'nin IAbpDistributedLock servisi
public class MyService : ITransientDependency
{
    private readonly IAbpDistributedLock _distributedLock;

    public async Task MyMethodAsync()
    {
        await using (var handle = await _distributedLock.TryAcquireAsync("MyLockName"))
        {
            if (handle != null)
            {
                // Kilit alındı
            }
        }
    }
}

ABP, DistributedLock kütüphanesini sarmalayarak Redis ve ZooKeeper desteği sunar .


7. En İyi Pratikler

Pratik Açıklama
Kilit Adını Doğru Seçin Kaynağa özgü, benzersiz ve anlamlı isimler kullanın.
TTL Belirleyin İşlem süresinden uzun, ancak makul bir TTL (ör. 30 saniye) ayarlayın.
Fencing Token Kullanın Kritik kaynaklar için monotonik token ile eski sahiplerin erişimini engelleyin .
Idempotency Sağlayın Kilit süresi dolduktan sonra işlem devam ederse, işlemin tekrarı zararsız olsun.
Kilit Süresini Uzatın (Redisson Watchdog) Uzun süren işlemler için kilit otomatik yenilenmeli .
Granülerliği Doğru Ayarlayın Çok geniş kilit (tüm tablo) yerine kaynak bazlı kilit (satır bazlı) tercih edin.
Hata Yönetimi Kilit alınamazsa ne yapılacağını netleştirin (bekle, atla, hata dön).
Monitoring ve Loglama Kilit bekleme sürelerini ve başarısızlıkları izleyin .
Kritik Bölgeyi Kısa Tutun Kilit içindeki işlem ne kadar kısa olursa, çekişme o kadar az olur .

8. Ne Zaman Hangisi?

Senaryo Öneri
Yüksek performans, hafif tutarlılık yeterli Redis Redlock (veya Redisson)
Güçlü tutarlılık, koordinasyon görevi ZooKeeper
Zaten Redis altyapısı varsa Redis
Zaten ZooKeeper altyapısı varsa ZooKeeper
.NET projesi, kolay entegrasyon DistributedLock kütüphanesi
Kritik finansal işlem ZooKeeper + Fencing Token

Sonuç

Dağıtık kilitleme, çok instance'lı sistemlerde kaynak tutarlılığını korumak için vazgeçilmez bir yapı taşıdır. Redis Redlock, yüksek performans ve düşük gecikme sunar; ancak AP modeli nedeniyle ana-yedek geçişlerinde kilit kaybı riski taşır . ZooKeeper, CP modeli ve geçici sıralı düğümler sayesinde güçlü tutarlılık ve otomatik kilit temizliği sağlar; ancak performansı Redis'e göre daha düşüktür . Her iki yaklaşımda da fencing token kullanarak çit sorununu çözmek ve idempotency ile işlem tekrarlarına karşı dayanıklılık sağlamak kritik öneme sahiptir. .NET ekosisteminde DistributedLock kütüphanesi, her iki arka ucu da destekleyerek entegrasyonu kolaylaştırır .

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