Bu makale kapsamında, Trendyol.com çatısı altında geliştirdiğimiz ve legacy uygulamalarımızı kolay bir şekilde scale edebilmemizi sağlayan Loki kütüphanesinden sizlere bahsedeceğiz.
Öncelikle problemimiz neydi ve nasıl çözdük?
Sistemimiz içerisinde henüz redevelopment yapılmasına öncelikli olarak ihtiyaç duymadığımız bazı windows service’lerimiz mevcuttu. Bu service’ler üzerinde redevelopment yapıp vakit kaybetmek istemiyorduk ama aynı zamanda da, gün geçtikçe büyüyen transaction’lar karşısında scale edebilme ihtiyacımız da doğmuştu.
Problemimizi çözmek için o an ilgili service’e odaklanmayıp, basit bir kütüphane oluştursak ve bu kütüphane hem method bazlı lock işlemi, hem de uygulama bazlı bir lock işlemi yapabilse nasıl olur? diye bir düşündük. Üstelik ilgili lock işlemlerini in-memory yerine distributed bir ortamda ve ortak bir yerde persist edersek, bu işlem bizim aynı veriler üzerinde çalışan service’lerimizin de artık distribute bir şekilde çalışabilmesini de sağlar diye bir fikir edindik. Tartışma sonucunda edindiğimiz fikirleri kağıt üstüne dökerek oluşturulacak olan kütüphane için sequence diyagramı çizip, üzerinde konuşmaya başladık.
Konuşma sonucunda lock işlemi için kullanacak olduğumuz key’leri, Redis üzerinde persist etmeye karar verdik. Bu kararın ardından ise, peki ya Redis down olursa? veya bir network split problemi yüzünden o an Redis’e ulaşamazsak ne olur? gibi sorulara geldik. Bu tarz problemlere de önlem alabilmek adına defensive davranarak, key’leri Redis üzerinde persist ederken, async bir şekilde de bunu secondary olarak davranacak başka bir yerde daha persist edelim kararına vardık ve tüm bu konuşmaların ardından aşağıdaki gibi bir secondary sequence diyagramı elde ettik ve adına Loki dedik. 🙂
Loki’yi artık kısaca tanımlamak gerekirse:
Distributed sistemler üzerinde kolay bir şekilde lock işlemlerini handle etmeye yarayan bir kütüphanedir diyebiliriz artık.
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ısmı, “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.
Yukarıdaki diyagram üzerinde 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.
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 redisEndPoints = new List { 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.
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.
Proje geliştirmeye açık olup, sizler de github üzerinden katkıda bulunabilirsiniz.
Proje adresi: https://github.com/Trendyol/Loki