Software Architecture

Distributed Lock Nedir? Redis ile .NET'te Yarış Durumu Nasıl Kesilir?

Ürfet Demirtaş 4 dk okuma

Distributed lock, birden fazla süreç veya sunucunun aynı kritik bölgeye aynı anda girmesini engelleyen paylaşılan kilittir. Tek makinedeki lock nesnesi başka instance'ı görmez. İki pod aynı siparişi rezerve eder, aynı iadeyi işler, aynı fatura numarasını üretir. Kilidi Redis veya benzeri ortak bir yerde tutmazsanız yarış durumu production'a sızar.

Idempotency key istemcinin niyetini tanır. Distributed lock sunucu tarafında "şu an bu kaynağı ben işliyorum" der. İkisi birbirinin yerine geçmez. Ödeme ve siparişte anahtar tarafını idempotency yazısında kurmuştum. Burada o kapının arkasındaki yarışı kesiyorum.

Ne zaman gerekir?

  • Stoktan son birimi düşmek
  • Aynı siparişe iki worker'ın bakması
  • Fatura / fiş numarası üretmek
  • Periyodik job'un üç replica'da birden çalışması
  • Harici API'ye "aynı anda bir" kota

Salt okuma, cache doldurma ve zaten veritabanı unique index'iyle korunan basit insert için distributed lock şart değildir. Kilidi her metoda saran mimari, latency ve kilit sızıntısı üretir. Önce unique constraint ve idempotency yeter mi bakın. Yetmiyorsa kilidi dar bir kritik bölgeye koyun.

Redis SET NX EX: pratik çekirdek

En sık kullanılan biçim şudur: anahtarı yalnızca yoksa yaz (NX), yanına süre koy (EX). Yazabildiyse kilit sizdedir. Yazamadıysa başkası tutuyordur.

public sealed class RedisDistributedLock
{
    private readonly IConnectionMultiplexer _mux;
    public RedisDistributedLock(IConnectionMultiplexer mux) => _mux = mux;

    public async Task<bool> TryAcquireAsync(
        string resource,
        string ownerToken,
        TimeSpan ttl,
        CancellationToken ct = default)
    {
        var db = _mux.GetDatabase();
        // SET lock:order:42 <token> NX EX 30
        return await db.StringSetAsync(
            $"lock:{resource}",
            ownerToken,
            ttl,
            When.NotExists);
    }

    public async Task<bool> ReleaseAsync(string resource, string ownerToken)
    {
        const string lua = @"
if redis.call('get', KEYS[1]) == ARGV[1] then
  return redis.call('del', KEYS[1])
else
  return 0
end";
        var db = _mux.GetDatabase();
        var result = (int)(long)await db.ScriptEvaluateAsync(
            lua,
            new RedisKey[] { $"lock:{resource}" },
            new RedisValue[] { ownerToken });
        return result == 1;
    }
}
var token = Guid.NewGuid().ToString("N");
var resource = $"order:{orderId}";

if (!await _lock.TryAcquireAsync(resource, token, TimeSpan.FromSeconds(30)))
    return Conflict("Sipariş başka bir işlemde.");

try
{
    await _orders.ReserveStockAsync(orderId);
}
finally
{
    await _lock.ReleaseAsync(resource, token);
}

Sahiplik token'ı şarttır. TTL dolmadan iş bitince kilidi silersiniz; ama yalnızca sizin token'ınızsa. Token kontrolsüz DEL, A'nın işi sürerken B'nin kazara A'nın kilidini düşürmesine yol açar. Lua ile get+del atomik olur. İki ayrı komut yarış bırakır.

TTL: unutulan kilidin sigortası

Process çökerse finally çalışmaz. TTL yoksa kilit sonsuza kadar kalır; sonraki tüm istekler Conflict yer. TTL iş süresinden uzun, felaket sürelerinden kısa olsun. 30–60 saniye çoğu rezervasyon için yeter. İş 30 saniyeyi aşıyorsa kilidi uzatın (watchdog / heartbeat). Uzatmayı unutup işe devam etmek, TTL bitince ikinci worker'ın aynı kayda girmesi demektir.

  • Kilit al
  • Kritik işi yap
  • Token ile bırak
  • İş bitmeden TTL dolmasın; dolacaksa uzat veya işi böl

Redlock ve tek Redis

Redlock, birden fazla bağımsız Redis düğümünde çoğunluk oyuyla kilit alma fikridir. Tek primary Redis'iniz ve net failover politikanız varsa çoğu ürün ekibi tek instance SET NX EX ile yürür. Redlock'u "daha bilimsel" diye varsayılan seçmeyin. Operasyon maliyeti artar; yanlış kurulmuş quorum, yanlış güvenlik hissi verir.

Asıl tehlikeli senaryo şudur: Redis restart olur, bellek boşalır, kilitler uçar, iki pod aynı anda girer. Kalıcılık (AOF) bunu yumuşatır ama kilidi veritabanı constraint'inin yerine koymaz. Stok için mümkünse UPDATE ... WHERE Stock >= @qty ve etkilenen satır sayısı kontrolü kilidin yanında dursun. Kilit hızı keser; constraint son kapıdır.

SQL mi Redis mi?

  • Kısa süreli, çok sık, düşük latency: Redis.
  • İş zaten aynı SQL transaction içinde ve satır kilidi yeterli: UPDLOCK, ROWLOCK veya optimistic concurrency.
  • Job'un günde bir kez çalışması: Redis kilidi veya DB'de "lease" satırı.

Stok gösterimini Redis cache ile hızlandırdığım yazıda cache vardı, kilit yoktu. Cache yanlış sayıyı hızlı gösterir; kilit yanlış düşümü keser. İkisini karıştırmayın. Cache yazısını stok ve Redis yazısında bırakmıştım.

Production'da kırılan yerler

Kilidi aldıktan sonra HTTP cevabını bekletip uzun dış çağrı yapmak kilit süresini şişirir. Dış banka çağrısını kilit altında tutmayın. Kilitle sadece "bu sipariş şu an rezerve ediliyor" bayrağını ve yerel yazımı koruyun. Uzun işi kuyruğa alın; kuyruk tüketicisinde yine dar bir kilit veya idempotent işleyici kullanın.

Aynı resource adını iki farklı iş için paylaşmayın. lock:order:5 hem stok hem fatura için kullanılırsa biri diğerini gereksiz bloklar. İsmi işe göre seçin: lock:stock:sku:88, lock:invoice:daily:2026-11-06.

Logda resource ve token'ın kısa özetini yazın. "Kilit alınamadı" alarmı, hangi sku'nun sıkıştığını söylemezse işe yaramaz. İstek zincirini correlation id ile bağlarsanız kilit çatışmasını tek sorguda görürsünüz.

Kısa cevaplar

Distributed lock nedir?

Birden fazla sunucu veya sürecin aynı kritik işi aynı anda yapmasını engellemek için ortak bir depoda tutulan kilittir. Yerel lock nesnesi başka instance'ı görmez.

Redis ile nasıl alınır?

SET resource token NX EX seconds. NX, yoksa yaz demektir. Yazabilen taraf kilidi alır. Bırakırken token eşleşmesi Lua ile atomik silinir.

TTL neden zorunlu?

Process çökünce kilidi kimse silmez. TTL, unutulan kilidin otomatik süresidir. İş TTL'den uzun sürecekse heartbeat ile uzatın.

Idempotency key ile farkı ne?

Idempotency key istemcinin aynı niyeti tekrar gönderdiğini tanır. Distributed lock sunucular arası eşzamanlı girişi keser. Ödeme ve stokta çoğu zaman ikisi birden gerekir.

Redlock şart mı?

Hayır. Tek iyi işletilen Redis ve yanında DB constraint çoğu ürün için yeter. Redlock, çok düğümlü özel senaryolar içindir; varsayılan değildir.

Tüm yazılar →