StackExchange Redis ile Distributed Caching
Uygulamalarımızı geliştirirken performans, ölçeklenebilirlik gibi kavramlar üzerinde sıkça durmaktayız. Bu kavramları geliştirme yöntemlerinden birisi olan ön belleğe alma (caching) işlemi sıkça kullanılan bir yöntemdir.
Caching Nedir?
Caching (önbellekleme) işlemi, sık kullanılan verilerin sürekli veri tabanı, dosya gibi veri kaynağına bağlanılıp alınmaması ya da uzun hesaplamalar sonucu ortaya çıkan veriler için tekrar hesaplamaların yapılmaması için verilerin geçici olarak bellekte saklanması işlemidir.
Birçok farklı önbellek türü veya düzeyi olabilir. Bunlar; tipik bir web sitesinde tarayıcı önbelleği, sunucu düzeyinde önbellek veya bir veri tabanı önbelleği olabilir. Bu unsurların birbirinden bağımsız olmasına rağmen, hepsinin aynı hedefe ulaşmak için çalıştıklarını bilmek önemlidir; daha hızlı yanıt vermek, performans gibi nedenler.
Geliştirilen yazılımlarda ilk akla uygulama ile cache verilerini aynı sunucuda saklamaktır. Private caching veya in memory caching diyeceğimiz bu yöntem küçük çaplı ve tek sunucuda çalışması gereken uygulamalar için yeterliyken orta ve büyük çaplı projeler için yeterli gelmemektedir. Bu noktada tercih edilmesi gereken yöntem
Distributed Caching Nedir?
Distributed caching, önbelleğe alınacak verilerin farklı sunucularda tutulması işlemidir yani önbellekteki veriler uygulamanın barındığı sunucuda saklanmaz.
Verilerin başka sunucuda tutuluyor olması birçok avantajı beraberinde getirmektedir.
- Çalışan uygulamanın birden fazla örneği (instance) olabilir.
- Verinin önbelleğe alınma işlemi, uygulamalardan bağımsız olacağı (farklı sunucularda tutulacağı) için veriler arası tutarsızlığı önlemektedir. Sonuç olarak bütün istemciler aynı örnek üzerinden veri çağrımı yapmaktadır.
- Ölçeklenebilir geliştirme yapmamıza da olanak sağlar.
- Cache servise istekte bulunan uygulamaların kapanması durumunda önbellekteki verilere herhangi bir zarar gelmeyecektir.
- İstemci uygulamaların (yukarıdaki diyagramdaki A ve B örneği gibi) artırılması ya da azaltılması önbelleğe alınmış verilere etki etmemektedir. İstediğimiz kadar uygulama ayağa kaldırabiliriz. (örneği oluşturabiliriz)
Verilerin farklı sunucu üzerinde depolanmasının bazı dezavantajları da vardır.
- Önbelleğe erişim, verilerin direkt olarak uygulama sunucusunda tutulduğu duruma göre daha yavaştır uzak sunucuda olmasından dolayı.
- Uygulama gereksinimlerine ayrı bir sunucu eklemek karmaşıklığı artırabilir.
Yukarıdaki durumlar her ne kadar dezavantaj gibi görünse de işlemlerin başka sunucuda yapılıyor olmasının sağladığı yarar dolayısıyla yukarıdaki dezavantajları göz ardı edilebiliyor. Örneğin gecikme durumu için bahsedilecek olursa bu tür durumlar dikkate alınacak bir gecikme değil ve farklı sunucularda tutulması veri tutarlılığı gibi önemli konulara çözüm sağladığı için dikkate alınmayabiliyor.
In Memory Cache
Yazıdaki örnekleri .NET Core üzerinden vereceğim. Genel mantık benzer olduğu için farklı dil ve frameworklerde de benzer şekilde çalışan kütüphaneler kullanabilirsiniz.
.NET Core kendi içerisinde IMemoryCache
arayüzünü uygulayan bir servise sahiptir. Önbelleğe alınacak verileri uygulamanın bulunduğu sunucu belleğinde tutmaktadır bir nevi private caching durumu.
Distributed caching mekanizmasını sağlamak için de IDistributedCache
arayüzünü uygulayan bir servise sahiptir fakat bu arayüze sahip servis kısıtlı işlemleri uygulamaktadır.
Yukarıda Microsoft.Extensions.Caching.Distributed namespace altındaki arayüzü IDistributedCache
görünmektedir. Gerçekleştirdiği işlemlere bakıldığı zaman gerçekten de kısıtlı işlemler içermektedir. Sunduğu imkanlarda; bütün verilere erişmek, anahtar değerleri elde etmek, Redis gibi NoSQL veri tabanı için uygun veri yapısının seçilmesi gibi temel ihtiyaçların olmadığı görülmektedir. Bu yüzden daha esnek, ihtiyaçları karşılayabilir yapılar kullanılmaktadır tıpkı StackExchange.Redis gibi.
StackExchange.Redis ile Distributed Cache
StackExchange.Redis kütüphanesi açık kaynaktır ve Stackoverflow geliştiricileri tarafından geliştirilmektedir. Kaynak kodlarına GitHub üzerinden de erişilebilmektedir. Kullanılmasının başlıca nedenlerinden birisi Stackoverflow geliştiricileri tarafından geliştirilmesi ve esnek olmasıdır.
Örnek uygulama .NET Core Web API 3.1 üzerinden olacaktır, Redis’e yapılacak veri işlemleri için StackExchange.Redis kullanılacaktır.
Uygulamaya başlamadan önce bu uygulamanın kaynak kodlarına GitHub üzerinden erişebilirsiniz.
Projenin oluşturulması için dotnet new webapi -o NetCoreRedisStackExchange
komutunu, ilgili kütüphanenin indirilmesi için de dotnet add package StackExchange.Redis --version 2.1.30
komutunu kullanabiliriz.
Oluşturduğumuz proje içerisinde CacheService adında bir klasör oluşturarak caching için gerekli implementasyonları gerçekleştirebiliriz.
Yukarıda caching işlemleri için bir arayüz tanımlanmıştır ve içerisinde; veriyi getirme, silme, bütün verilerin silinmesi gibi temel metotlar yer almaktadır. Arayüz içerisinde tanımlanmasının sebebi uygulama boyunca işlemlerinin soyutlanmasıdır. İsteğe bağlı başka bir sunucu üzerindeki belleğe de yazılabilir, uygulamanın çalıştığı belleğe de.
Bu arayüzü implemente eden (uygulayan) servisimizi tanımlamadan önce Redis sunucusunu ve veri tabanını temsil edecek bir sınıf oluşturalım. CacheService klasörü altında Redis adında bir klasör oluşturarak ilgili sınıfı oluşturabiliriz.
Yukarıdaki koda baktığımız zaman;
CreateRedisConfigurationString
metotu appsettings.json
dosyası üzerinden config bilgilerini okumaktadır.
ConnectionMultiplexer
sınıfı bizlere Redis sunucusuna bağlanmayı, ilgili veri tabanını kullanmayı, bu veri tabanları üzerindeki işlemleri yapmamızı sağlayan metotları barındırmaktadır.
IDatabase
arayüzü, bizlere ConnectionMultiplexer
üzerinden id değeri ile referansını aldığımız veri tabanının referansını tutar, veri tabanına ilgili yazma ve okuma gibi işlemler burada gerçekleşir.
Yukarıdaki örnekte FlushDatabase
adında bir metot bulunmaktadır. ICacheService
arayüzüne baktığımız zaman Clear
adında bir metot bulunduğunu ve ilgili veri tabanındaki bütün değerleri sildiğini biliyoruz. Veri tabanına yazma, silme gibi işlemler IDatabase
üzerinden gerçekleştirilmesine rağmen bizlere veri tabanı üzerindeki bütün verilerin silinmesi özelliğini sağlamamaktadır. Bunun için ConnectionMultiplexer
ile sunucuya bağlanıp ilgili veri tabanındaki bütün verilerin silinmesini sağlayabiliriz.
RedisServer
sınıfı hazır olduğuna göre, ICacheService
arayüzünü uygulayan servis sınıfı oluşturulabilir.
Yukarıdaki koda bakıldığı zaman RedisServer
sınıfının dependency injection ile alındığı görülmektedir.
Redis’e verileri yazarken string formata çeviriyoruz (Serialization), okurken de string formatı istenilen tipe çeviriyoruz (Deserialization). Burada string veri tipini kullandık, istenildiği taktirde Redis’e ait diğer veri yapıları da kullanılabilir. Bu işlemleri gerçekleştirirken de RedisServer
sınıfındaki IDatabase
arayüzünün referansını tutan field ile gerçekleştirdik.
Gerekli implementasyonlar yapıldıktan sonra bunları Startup
sınıfındaki ConfigureServices
metotu içerisinde tanımlamamız gerekiyor. Servis tanımlamaları aşağıdaki gibi olacaktır;
Gerekli servis tanımlamaları yapıldıktan sonra senaryo olarak bir web servis üzerinden kitap listesi paylaştığımızı ve kitap listesine erişmeden önce Redis üzerindeki kontrolü gerçekleştirdiğimizi, servis üzerinden yeni bir kitap eklendikten sonra da Redis üzerindeki eski verilerin silindiğini var sayalım.
Senaryo süresince kitapları temsil edecek sınıfımız Models klasörü içerisinde aşağıdaki gibi olacaktır.
Model sınıfımız da tanımlandığına göre, BooksController
adında bir Controller
oluşturalım ve içerisinde bir kitap listesi tutulsun. (Veri tabanı implementasyonu ile konunun gereksiz uzatılmaması için)
Yukarıdaki koda bakıldığı zaman ICacheService
arayüzünün dependency injection ile alındığı, uygulama başladığında listenin birkaç veri ile de doldurulduğu görülmektedir.
Verilerin servis üzerinden paylaşılması esnasında; önce Redis üzerinde kontrolü gerçekleştirilmektedir eğer Redis üzerinde varsa direkt oradan veriler çekilerek paylaşılacaktır yoksa kullanılan veri kaynağından verinin alınması gerçekleştirilecek ve daha sonra Redis üzerine yazılması yapılacaktır. Bir sonraki çağrımda ise veri kaynağına erişmeden direkt Redis üzerinden getirilecektir.
Yeni verinin eklenmesi durumunda ise önce veri ekleniyor daha sonra ise Redis üzerindeki veri siliniyor. Buradaki amaç veri tutarsızlığının önüne geçmek.
Temel isteklerden sonra Redis üzerindeki veriler aşağıdaki gibi olacaktır;
Bu uygulamanın kaynak kodlarına GitHub üzerinden erişebilirsiniz.