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 bozulmasını engellemek gerekir. Tek bir JVM içinde lock yeterli olurken, servis birden fazla sunucuya yayıldığında paylaşılan bir harici depo üzerinden mutex (karşılıklı dışlama) sağlanmalıdır. İşte Redis Redlock ve ZooKeeper, bu sorunu çözen iki farklı felsefeye sahip çözümdür.
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.
-
Aynı kaynağın iki kez işlenmesinin engellenmesi (ör. aynı siparişin çift işlenmesi).
-
E-ticaret stok yönetiminde aşırı satışın (oversell) önlenmesi.
-
Lider seçimi (leader election) veya ana-yedek geçişlerinde geçici bir "lider" belirlenmesi.
Temel Gereksinimler: Karşılıklı dışlama (mutual exclusion), kilitlenme koruması (deadlock freedom), hata toleransı (fault tolerance).
2. Redis Redlock: Çoğunluk Tabanlı Yaklaşım
A. Tek Instance Kilidin Sorunu
Basit bir Redis kilidi (SET resource NX PX 30000) tek bir instance üzerinde çalışır. Ancak ana-yedek (master-replica) mimarisinde bir yarış durumu oluşur: İstemci A ana düğümde kilidi alır, ana düğüm yedeğe çoğaltmadan çöker, yedek terfi eder, İstemci B aynı kilidi alır. İki istemci de aynı kilidi tuttuğunu sanır .
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ı:
-
Mevcut zaman alınır (T1).
-
Tüm N instance'a kilit isteği gönderilir:
SET resource <random_value> NX PX 30000. Her instance için kısa bir timeout (5-50 ms) kullanılır . -
Zaman tekrar alınır (T2) ve
elapsed = T2 - T1hesaplanır. -
Kilit şu koşullarda başarılı sayılır: En az N/2 + 1 instance'da kilit alınmış olmalı ve
elapsed < TTLolmalı . -
Kilit alındıysa, gerçek geçerlilik süresi:
validity_time = TTL - elapsed - clock_drift_allowance. -
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. Bu, başka bir istemcinin kilidini yanlışlıkla silmeyi engeller .
C. Redlock'un Sınırları ve Eleştiriler
-
Saat Kayması: Redlock, yerel saatlerin yaklaşık aynı hızda ilerlediğini varsayar. Ciddi saat kaymaları güvenliği riske atar .
-
Çit Sorunu (Fencing Problem): İstemci A kilidi alır, 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: Güçlü Tutarlılık ve Otomatik Temizlik
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 .
Çalışma Prensibi:
-
Tüm istemciler
/locks/job-lock/dizini altında geçici sıralı düğüm oluşturur:lock-0000000012. -
Her istemci, dizindeki tüm alt düğümleri okur ve sıralar.
-
Kendi düğümü en küçük sıraya sahipse, kilidi almıştır.
-
Değilse, yalnızca kendinden bir önceki düğümü dinler (izler) .
-
Önceki düğüm silindiğinde, istemci tekrar kontrol eder ve en küçük olup olmadığını görür.
-
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 .
ZooKeeper'ın Avantajları:
-
CP Sistemi: 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 .
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.
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 | 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 | Doğal adil (sıralı düğümler) |
| .NET Desteği | RedLock.net, DistributedLock.Redis | ZooKeeperDistributedLock (NuGet) |
| En Uygun Senaryo | Yüksek performans, hafif tutarlılık | 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, 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 .
6. .NET'te Dağıtık Kilit: DistributedLock Kütüphanesi
DistributedLock (madelson/DistributedLock), .NET ekosisteminde Redis ve ZooKeeper gibi farklı arka uçları destekleyen olgun bir kütüphanedir .
Redis ile Kullanım:
csharp
using DistributedLock;
using StackExchange.Redis;
var connection = ConnectionMultiplexer.Connect("localhost:6379");
var @lock = new RedisDistributedLock("my-resource-key", connection.GetDatabase());
await using (var handle = await @lock.TryAcquireAsync())
{
if (handle != null)
{
// Kritik bölge
}
}
ZooKeeper ile Kullanım:
csharp
using ZooKeeperDistributedLock;
var factory = serviceProvider.GetService<ZooKeeperLockFactory>();
using var distributedLock = factory.CreateLock("my-resource");
if (await distributedLock.TryAcquireAsync())
{
try
{
// Kritik bölge
}
finally
{
await distributedLock.ReleaseAsync();
}
}
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 (Watchdog) | Uzun süren işlemler için kilit otomatik yenilenmeli (Redisson watchdog). |
| Kritik Bölgeyi Kısa Tutun | Kilit içindeki işlem ne kadar kısa olursa, çekişme o kadar az olur. |
| Doğru Aracı Seçin | Performans mı, tutarlılık mı? Cevap, seçimi belirler. |
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. Unutmayın: Dağıtık kilit, %100 mutlak garanti değil, hata olasılığını kabul edilebilir seviyeye indiren bir araçtır; önemli olan, her olası hata senaryosu için bir geri dönüş planına sahip olmaktır.