.Net Core Ortamında Distributed Lock Mekanizmasını Redis Üzerinden Yapmak

Selamlar,

Bu makalede .Net Core ortamında, çoklu işlemlerin yapıldığı durumlarda örneğin thread veya tasklarda, ortak kaynak tüketiminde bir takım  problemlerin çıkmaması için kullanılan lock nesnelerini Redis üzerinden inceleyeceğiz. Asenkron lock işlemleri hakkında daha detaylı bilgiye bu makaleden erişebilirsiniz.

Şimdi gelin isterseniz parallel programlamada lock işleminin tam olarak ne işe yaradığını hep beraber inceleyelim.

Yukarıdaki komut ile .Net Core Console “lockRedis” projesi oluşturulur. Bu makalede local makinanızda, Redis’in kurulu olduğu varsayılmaktadır. Mac ortamında Redis ==> Terminalden “redis-server” komutu ile lokalde ayağa kaldırılır.

Proje içinde Redis kütüphanesi için “StackExchange” kullanılmıştır. Aşağıdaki komut ile ilgili kütüphane, projeye eklenebilir.

1-) Local Redis’e .Net Core Ortamında Connect olmak : Aşağıda görüldüğü gibi Redis sunucu için 6379 portundan localhost’a bağlanılmıştır.

2-) Redis’e herbir process için yani Unique key ile bir değerin atanarak, Lock işleminin yapılması.

Aynı anda çalışan asenkron durumlarda senkron davranan Redis, Taskların doğru şekilde yönetilmesi için gerekli olan Lock işleminin yapılabileceği en doğru teknolojilerden biridir.

  • “StringSet(key, value, expireTime, When.NotExists)”: Tekil olarak ilgili process için oluşturulan “key“, atanacak herhangi bir value değeri, lock işleminin sonlanacağı “expireTime” time out süresi ve “When.NotExists” ile kayıt yok ise ilgili değerin redise atımı gerçekleştirilir. Son olarak geriye eğer redis’e kayıt atılmış ise “true” cevabı dönülür. Hiçbir kayıt işlemi yapılmamış ise “false” cevabı dönülür.

Not: Burada atanan “key“, locklama yapılacak her bir process için farklı olmalıdır. Böylece farklı işlemler birbirilerini engellemezler.

3-) Bu örnekte 8 tane Pararllel process’in aynı anda başlatılıp, ilk gelen Task’a kitleme işlemine izin verilmesi ve sonraki taskların hiçbir şekilde girişine izin verilmemesi sağlanmıştır. Takii, Redis’e atılan ilgili process’e ait lockKey’in expire süresi dolana kadar. Böyle bir durumda, Redis ilgili process’e ait key’e göre boş olacağa için, yeni değerin Redis’e atılması ve geriye “true” değerinin dönülmesi sağlanmıştır. Timeout süresi 1dk olarak belirlendiği ve ilgili process key’e ait değer redis’de var olduğu için, diğer tasklar ilgili methodun içine girememiş ve istenen locklama işlemi başarı ile sağlanmıştır.

1.Durum lock İşlemi: İlk gelen Task kitleme işlemini yapar. Böylece diğer Taskler kilitli durumunda kalırlar.

2.Durum: Uygulama 1 dakikadan önce tekrardan çalıştırıldığında, Redis’de halen değer olduğu için bu sefer tüm Tasklar kilitli durumunda kalmıştır.

Eğer yukarıdaki kodlardan lock işleminin sonunda ==> “Connection.GetDatabase().KeyDelete(lockKey)” ilgili satır açılır ise, redis’e 1 dakikalık atılan key Expire Date beklenmeden temizlenmiş olunur. Bu durumda uygulama, tekrardan çalıştırıldığında 1.Durum’un aynısı gerçekleşir ve ilk gelen task içeri alınıp redis’e yazılır ve diğer tasklar için lock’lama işlemi yapılır. Kısaca işlem bitince “lock” nesnesi, redis timeout süresini beklemek yerine manuel olarak kaldırılır.

Şimdi gelin yeni örnek ile Redis üzerinde lock işlemini detaylıca inceleyelim. .Net Core Console bir application oluşturulur.

Senaryo: 4 Task aynı methodu farklı parametreler ile paralel ve asenkron olarak çalıştıracaktır. Method içinde yapılacak lock işlemi, her task için farklı oluşturulup birbirlerini kitlemeleri önlenecektir. Yapılacak olan lock işlemi ile, farklı yerlerden çağrılan aynı tasklar, birbirlerini bekleyecek ve aynı işi yapan tasklar birbirlerini ezmeyeceklerdir.

Part 1: 

  • redisConnection(): Local Redis sunucusuna bağlanılır.
  • redisLock(): Gönderilen string key değerine göre, ilgili redis’in dolu olup olmadığına bakılır. Eğer boş ise belirtilen zaman aralığı için(“expiration”), ilgili key’e ait tanımlanmış value değeri atanır ve true değeri dönülür. Yani lock durumunda değildir. Eğer dolu ise “false” değeri dönülür. Yani lock durumundadır.
  • CounterByMultiTask(): Her bir task’in çağracağı method, budur. Değişen parametreler, lock işleminin yapılacağı her bir task’e özel “lockKey”, her bir task’in ismi “taskName”, redisde saklanacağı süre “expireTime” ve her bir “for” döngüsünde beklencek süre, “delay”‘ parametreleri ile tanımlanır. 1’den 10’a kadar yapılan saydırmada, her bir artım değeri, ilgili task’in ismi ile ekrana yazdırılır. Bu işleme başlamadan önce redis lock kontrolü yapılır. Yani gönderilen key değerine göre redis’de herhangi bir kaydın olup olmadığına bakılır ve kayıt yok ise, girilip henüz lock olmadığını belirten “true” değeri dönülür. Eğer aranan key’e ait bir kayıt var ise, girilemiyeceğini, yani ilgili methodun çalıştırılamıyacağını gösteren “false”değeri dönülür. Böylece aynı isimli Taskler birbirlerini ezemezler.

Part 2:

RunAllTaskAsync() : Tüm Tasklar farklı isim ve zaman aralıkları ile TaskList’e dolarlar. Parallel olarak “Task.WhenAll(TaskList)“‘ile çalıştırılan tüm tasklar, birbirlerini ezmeden Redis’de her birine ait keyler ile (lock) işlemine tabi tutulurlar. Static Main() methodu konsolda henüz asenkron programlamayı desteklemediği için await kullanılamayıp, bekleme amaçlı RunAllTaskAsync().Wait() methodu çağrılmıştır.

Not: Burada dikkat edilmesi gereken durum “Task Senkron”‘dur. Çünkü Task2 ile ayni (lock) key olan “lockTask2” değerine sahiptir. “Task2″‘nin, “Task Senkron”‘dan önce tanımlanmasından dolayı, ilkin Task2 çalışacak ve aynı lock key’e sahip olan “Task Senkron” for döngüsüne giremeyecektir. Bu neden ile “Task Senkron” ekrana aşağıda görüldüğü gibi basılmayacaktır.

Ekran Çıktısı:

Eğer aşağıdaki methoddaki (isLocked)  yani lock’key kaldırılır ise, aynı key’e sahip threadler birlikte çalışacaklar ve [Task2 – Task Senkron] ikisi birden ekrana basılacakdır. Çünkü aynı keye’e sahip Taskları kitleyen “lock” ortadan kaldırılmıştır.

Ekran Çıktısı:

En güzel senaryolu son sakladım :) (Lock) koyduğumuz durumda “Task Senkron” ekrana hiç basılmamaktadır. Çünkü Task2’nin (Lock) mejanizmasına takılmaktadır. Peki biz aşağıda görüldüğü gibi aynı lockKey’in geldiği durumlarda, yani aynı Task işlemleri için kitlenme durumu olduğu zaman, “while()” ile önceki Task’ın lock’ının kalkmasını yani redis’e atılan aynı Key’in, TimeOut’a uğramasını sürekli kontrol ederek beklersek ne olur?

Cevap: Önce Task1 ve Task2 ekrana basılır. Sonra While() ile Task2 lock’ın kalkması, RedisKey timeout süresi kadar beklenip, “Task Senkron” ve “Task3″‘ün de aşağıdaki gibi ekrana basılması sağlanır.

Ayrıca istenir ise, Task2 ve diğer tüm taskların saydırma işi bittiği zaman, redisde oluşturulan lock işlemi “Connection.GetDatabase().KeyDelete(lockKey)” komutu ile kaldırılıp, redis expire süresi beklenmeden diğer Taskların ilgili methoda girmesi sağlanabilir. Bu hem projeyi hızlandırmakta, hem de redisin üzerindeki yükü kaldırmaktadır.

Geldik bir makalenin daha sonuna. Bu makalede, Redisin asenkron ve Paralle Tasklar için nasıl Lock objesi olarak kullanılabileceğine, Lock nesnesinin kaldırılmasının yani var olan task’ın işinin bitirilmesinin nasıl beklenebileceğini ve son olarak MultiThread her bir task için, Redis üzerinde nasıl kolaylık ile “lock” objesi oluşturulabileceğini gördük.

Yeni bir makalede görüşmek üzere hepinize hoşçakalın.

Source Code: https://github.com/borakasmer/TaskRedisLock.git

Kaynaklar: https://redis.io/topics/distlock, https://www.c-sharpcorner.com/article/creating-distributed-lock-with-redis-in-net-core/

Herkes Görsün:

Bunlar da hoşunuza gidebilir...

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir