İçeriğe geç

C# Loki ile Service Bazlı Distributed Locking

Merhaba arkadaşlar.

Bu makale konumda sizlere Trendyol.com çatısı altında geliştirip, bazı uygulamalarımızda kullandığımız Loki kütüphanesinden bahsetmek istiyorum.

Loki Nedir?

Loki’yi kısaca tanımlamak gerekirse:

Distributed sistemler üzerinde kolay bir şekilde lock işlemlerini handle etmeye yarayan bir library’dir.

Loki’yi biraz daha açmak gerekirse eğer, shared bir resource üzerinden işlem yapan uygulamalarımız var ise ve bu uygulamalarımızı performans ve network split problemlerinden dolayı kolay bir yoldan scale etmek istiyorsak, işte bu noktada bize yardımcı olmaktadır.

Peki Loki Nasıl Çalışıyor? ve Consistency’i Nasıl Sağlıyor?

Loki’nin çalışma mantığı aslında oldukça basit. Sizden sadece querying yaptığınız kısımı, “Locking.Instance.ExecuteWithinLock” method’u ile wraplemenizi istemektedir. Loki, şuan altyapısında primary locking handler olarak Redis ile çalışmaktadır. Secondary locking handler olarak ise MSSQL implementasyonu bulunmaktadır. Loki’nin gerçekleştirdiği işlem ise wrap’lenen code bloğunu execute etmeden önce, initialize sırasında belirteceğimiz bir “serviceKey” ile Redis locking handler’ı üzerinde bir key persist etmektedir. Eğer key’i persist edebildi ise ilgili code bloğunu execute etmektedir ve işlem sonunda ise ilgili key’i Redis üzerinden kaldırmaktadır. Dolayısıyla key’i persist edemez ise, bu key ile ilgili bir işlem sürdüğünü anlamaktadır ve ilgili code bloğunu execute etmemektedir.

Secondary kısmına geçmeden önce dilerseniz bir sequence diyagramına bakalım.

Burada primary olan Redis üzerine gelen “Lock()” ve “Release()” isteklerine dikkat çekmek istiyorum.

  • 1. senaryoda eğer Redis üzerinden sorunsuz bir şekilde lock alabilirse, arka planda async bir şekilde consistency için secondary persistence store üzerinde de bir lock işlemi gerçekleştiriyor. Release kısmında da aynı işlem geçerlidir.
  • 2. senaryoda ise eğer Redis üzerinden herhangi bir lock alamıyorsa ki bunun anlamı başka bir uygulama şuan üzerinde çalışıyor, bunun için secondary’e hiç bir şekilde gitmeyip code bloğunu execute etmiyor.
  • 3. ve son senaryo için ise Redis üzerinden lock almaya çalışırken herhangi bir network probleminden dolayı bir hata alırsa, consistency’i sağlamak adına direkt olarak sync bir şekilde secondary’deki persistence store üzerine gidiyor. Release işleminde de aynı senaryo geçerli.

Yukarıdaki senaryolar doğrultusunda x bir uygulama n tane makine üzerinde çalışırken, aynı datalar üzerinde işlem yapmadan kolay bir şekilde multiple olarak distributed çalışmaları sağlanmış oluyor. Tabi bunun için ilgili business doğrultusunda işlediğiniz dataları, bir şekilde flaglemeniz gerekmektedir.

Implementasyon Aşaması

Şuan nuget.org üzerinde iki adet paket mevcuttur. Bunlardan birisi “LokiNet” diğeri ise “LokiNet.MSSQL” paketleridir. Nuget Package Manager üzerinden erişebilir ve kurulumu gerçekleştirebilirsiniz. Loki’nin kullanımı, mantığında da olduğu gibi gayet basit. İlk olarak uygulama ayağa kalkarken, “LokiConfigurationBuilder” ile aşağıdaki gibi initialize etmek gerekiyor.

List<EndPoint> redisEndPoints = new List<EndPoint>
{
    new DnsEndPoint("redisUri", redisPort)
};

LokiConfigurationBuilder.Instance.SetServiceKey("SimpleTestClient")
                        .SetPrimaryLockHandler(new RedisLokiLockHandler(redisEndPoints.ToArray()))
                        .Build();

Loki’ye burada “SimpleTestClient” key’i ile lock işlemlerini gerçekleştirmesi gerektiğini, “SetPrimaryLockHandler()” method’u ile de lock işlemlerini Redis üzerinden gerçekleştirmesi gerektiğini söylüyoruz. Secondary handler set etme mecburiyeti bulunmamakta fakat mevcutta bulunan MSSQL locking handler’ı da kullanmak isterseniz eğer:

LokiConfigurationBuilder.Instance.SetServiceKey("SimpleTestClient")
                        .SetPrimaryLockHandler(new RedisLokiLockHandler(redisEndPoints.ToArray()))
                        .SetSecondaryLockHandler(new MSSQLLokiLockHandler("connectionString"))
                        .Build();

şeklinde configure etmeniz yeterli olacaktır. Bununla birlikte MSSQL handler’ı için ihtiyaç duyacağı tablo script’i ise aşağıdaki gibidir.

CREATE TABLE [dbo].[LokiLockings](
    [ServiceKey] [varchar](50) NOT NULL,
    [CreationDate] [datetime] NOT NULL,
 CONSTRAINT [PK_LokiLockings] PRIMARY KEY CLUSTERED 
(
    [ServiceKey] ASC
))

Initialization işlemleri bu kadar. Artık tek yapmamız gereken uygulamamızdaki business akışına göre ister function bazında ister application bazında locking işlemini gerçekleştirmektedir.

Bunun için ise “Locking.Instance.ExecuteWithinLock()” method’unu aşağıdaki gibi kullanmamız yeterli olacaktır.

Locking.Instance.ExecuteWithinLock(() =>
{
    //do somethings..
},  expiryFromSeconds: 2);

“expiryFromSeconds” parametresi ile ise, isminden de anlaşılabileceği üzere ilgili lock işleminin belirli bir saniye sonunda otomatik olarak release olması sağlanmaktadır.

Bunlar dışında eğer secondary olarak sizde farklı bir persistence store kullanmak istiyorsanız, tek yapmanız gereken “LokiLockHandler” base class’ını aşağıdaki gibi inherit edecek yeni bir handler yazmaktır.

public class FooLockHandler : LokiLockHandler
{
    public override bool Lock(string serviceKey, int expiryFromSeconds)
    {
        //Lock operations
    }

    public override void Release(string serviceKey)
    {
        //Release operations
    }
}

Proje geliştirmeye açık olup, sizler de github üzerinden katkıda bulunabilirsiniz.

Umarım keyifli bir makale konusu olmuştur.

Proje adresi: https://github.com/GokGokalp/Loki

Kategori:.NETArchitecturalNesne Yönelimli Programlama (Object Oriented Programming)NoSQLPerformans (Profiling)

4 Yorum

  1. Selamlar Gökhan,

    Yazını çok beyendim. Siteni yeni keşfettim. Valla konular da çok güzel. Eline sağlık. Böyle güzel içerikleri bence daha çok tanıtmalısın ki herkes görsün. Mesela Twitter’ını bulamadım 🙂

    İyi çalışmalar. Hoşçakal..

    • Selam Bora,

      Teşekkür ederim öncelikle yorumun için, vakit buldukça yazmaya çalışıyorum. 🙂 Tanıtım konusunda haklısın. Twitter’ım var fakat biraz goygoy için kullandığımdan dolayı orada paylaşamıyorum pek.:) Sadece Linkedin ve bir kaç Facebook grubu…

  2. Veysel Veysel

    Merhaba,
    Lock istegi fail edince neden skip ettigini anlamadim. Lock isteyen taraf lock’u alana kadar bekler normalde. Orada fail yerine wait until lock falan gibi bisey olmasi gerekmez miydi ?

    Bir de ayni olmayan data uzerinde lock ihtiyaci icin bir real life example cok seker olur.

    • Merhaba Veysel bey, teşekkür ederim yorumunuz için.

      Aslında Loki’nin ilk versiyonunda lock alamama durumunda, lock tekrardan available olana kadar bekleme olayı vardı. Fakat kullandığımız pakette senaryomuza uymayan bir durum oluştuğu için kaldırmak zorunda kaldık. Bu paket üzerinde custom olarak tekrardan implemente edilebilir. Sizlerde GitHub üzerinden contribution’da bulunabilirseniz mutlu oluruz.

      İyi günler dilerim.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Bu site, istenmeyenleri azaltmak için Akismet kullanıyor. Yorum verilerinizin nasıl işlendiği hakkında daha fazla bilgi edinin.