8 Mart 2022 Salı

Distributed Locking

    S.A. Arkadaşlar,

    Bugün, iş yerinde üzerinde çalıştığımız ve bu çalışma sonucu çözüme kavuşturduğumuz bir konu olan "Distributed Locking" konusunu ele alacağız. Öncelikle lock nedir, avantaj ve dezavantajları, hangi çeşitleri vardır konularına bakacağız. Son olarak da C# implementasyonu ele alıp konuyu bitirmeyi hedefliyoruz. Hazırsak başlayalım.

     Lock'lama bir nesneye birden fazla isteğin geldiği noktalarda ortaya çıkan bir durumdur. Birden fazla uygulama bir nesneye erişiyor ve A uygulaması o nesneyi bırakmadan B uygulaması ele alıyorsa, set edilmemiş değeri aldığı için ortaya istenmeyen bir durum ortaya çıkabilir. Burada temel amaç aynı anda bir nesneye sadece bir uygulamanın erişmesi gerektiğidir. Böylece nesneyi eline alan A uygulaması işi bitmeden B uygulamasına bırakmıyor ve bu şekilde nesnenin değeri beklendiği gibi olabiliyor. A uygulaması bunu serbest bırakınca diğer herhangi bir uygulama bu sefer bunu ele alabilir.

    Bizim problemimiz ise dağıtık yapıda migration'lar başlatılıp veritabanları tamamen ayağa kalkmadan ayağa kalkan servislerin tekrar tekrar bazı servisleri çağırması sonucu gereksiz maliyetin oluşmasıydı. Ayağa kalkmayan servisleri birden fazla kez ayağa kaldırarak bunu çözüyorduk, fakat tahmin edeceğiniz üzere bu geçici bir çözümdü. Bununla birlikte bu problemi çözmenin birden fazla yolu da olabilir. Biz ise burada distributed locking kullanmayı daha uygun gördük.

    Lock'lama kullanmanın ortaya çıkaracağı tehlikeli sonuçlar olabilmesine rağmen verim ve doğruluk açısından önemini göz önünde bulundurmak gerekir. 

   * Verim derken neyden bahsediyoruz peki? Yukarıda da bahsettiğimiz gibi sistem aynı çağrıyı birkaç defa tekrarlayarak ayağa kalkabiliyordu, fakat bunların elbette maliyeti oluyordu. Bizim asıl  odaklandığımız nokta burasıydı. Çünkü lock elindeki işi bırakınca diğeri alabilir ve gereksiz çağrılara ihtiyaç kalmayacaktı ve öyle de oldu :) 

    * Doğruluk açısından ise yine yukarıda bahsettiğimiz üzere ele alınan verinin/nesnenin bozuk veya kirli veriler oluşmasını engeller. Açıkçası bizim böyle bir sıkıntımız yoktu, ama açıkçası bunun söz konusu olduğu durumlar daha fazladır. Bu vesileyle elimize aldığımız nesneyi koruyarak doğru bilgileri elde etmeyi garanti etmiş oluyoruz.

    Optimistic (iyimser) ve Pessimistic (kötümser) olmak üzere 2 tür lock'lama yapısı vardır. İyimser olan, işlerin yoluna gireceğini varsayarak ilerlerken, kötümser olan ise kaynağa erişmeden önce erişimi engeller ve işlem bittikten sonra bunu serbest bırakır. Aşağıya Martin Fowler'in sitesinden alınmış iyimser  ve kötümser durumları açıklayan 2 diyagram bırakıyorum.

https://www.martinfowler.com/eaaCatalog/optimisticOfflineLock.html

https://www.martinfowler.com/eaaCatalog/pessimisticOfflineLock.html


    EF Core gibi ORM'ler genel olarak iyimser yöntemi benimserken, kötümser yöntemi ele alan ise birden çok paket bulunmaktadır. Biz ise bu yazıda bu linkteki NuGet paketini kullanacağız.

    Bu paketin birden fazla implementasyonu var, biz ise Redis ile devam edeceğiz. Toplamda 4 metodu var. TryAcquireLock ve AcquireLock ve bunların async halleri. TryAcquireLock lock'ı eline alamazsa null dönerken AcquireLock ise farklı olarak LockNotGrantedException adında bir hata fırlatmaktadır.

    Name, Timeout, CancellationToken adına 3 parametre almaktadır. Name zorunlu bir alan olup lock'ın ismini alır. Lock ismi aynı işlem için aynı olmalıdır, aksi halde bunu farklı bir işlem olarak görüp lock'lamayı devreye almayacaktır. Lock'lama işleminin tehlikeli bir iş olduğunu en başta belirtmiştik. Deadlock olma ihtimali var ve böyle bir durumda belirli bir süre sonra bunun zaman aşımına düşmesi ve böylece öldürülmesi gerekmektedir. Timeout tam da bu noktada devreye girer. Belirlenen süre sonunda bu işlemi devreye alır. Varsayılan olarak değeri 30 saniyedir. CancellationToken son parametre olup "default" olarak set edilmiştir. 

    startup.cs, appsetting ve servis tarafındaki kodlarımızı paylaşalım. Buradan da açık kaynaklı örnek projemize erişebilirsiniz. 

Install-Package DistributedLock.Redis -Version 1.0.1 -- NuGet paketini indiriyoruz.
public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IDistributedLockProvider>(sp =>
    {
        //appsetting tarafından Redis bilgileri aşağıdaki çağrılabilir.
        var connection = ConnectionMultiplexer.Connect(configuration["Redis:Configuration"]);
        return new RedisDistributedSynchronizationProvider(connection.GetDatabase());
    });
}

"Redis": {
    "Configuration": "127.0.0.1"
}
namespace Demo
{
    public class MyService
    {
        private readonly IDistributedLockProvider _distributedLockProvider;
		
	public MyService(IDistributedLockProvider distributedLockProvider)
        {
            _distributedLockProvider = distributedLockProvider;
}         await using (var handle = await _distributedLockProvider.TryAcquireAsync("NameOfLock"))
        {         if (handle != null)         {         //your code         }         }     } }
    Kodları da paylaştığımıza göre yazın sonuna doğru gelmiş bulunmaktayız. Yararlandığımız kaynakları paylaşıp yazıyı bitirebiliriz.

    Deadlock'sız yapılar tasarlayıp kodlamak dileğiyle :) 

İlgililerine

    Kalplerin mühürlenmesiyle (lock'lanmasıyla :) ilgili şu yazıya bakabilirsiniz.

Kaynak

Hiç yorum yok:

Yorum Gönder