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.
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
}
}
}
}
İlgililerine
Kalplerin mühürlenmesiyle (lock'lanmasıyla :) ilgili şu yazıya bakabilirsiniz.
Hiç yorum yok:
Yorum Gönder