Merhaba arkadaşlar bu yazımında .net core üzerinde Redis kullanımı değineceğiz. Redis'in açılımı REmote DIctionary Server. In-Memory Cache gibi verileri key value olarak tutar. Avantajlı taraflarından bir tanesi veri tipleri vardır ve Distributed Cache olmasıdır. Böylelikle bir uygulamadan birden fazla instance ayağa kaldırsak bile tek bir redis server ile hepsine erişip veri tutarlılığını sağlayabilirsiniz. Kısaca redis tanımın yaptıktan sonra konumuza geçelim.
Windowsta Chocolatey ile Redis Kurulum
Redisin Windows serverler için yayımladığı resmi bir sürümü yok, sadece linux tabanlı versiyonunu yayınlıyorlar. Ancak bağımsız geliştiriciler tarafından yayımlanmış bir open source versiyonu var. Chocolatey paket yöneticisi. Şimdi bu adresteki yönergeleri takip ederek kurulumumu yapalım. https://chocolatey.org/install
1- PowerShell'i yönetici olarak çalıştırıp aşağıdaki komutu yazın.
Get-ExecutionPolicy
Bu komutumuz Get-ExecutionPolicy'nin kısıtlı olup olmadığını kontrol eder. Eğer komuttan Restricted dönerse aşağıdaki kodları kullanarak ByPass edin.
Set-ExecutionPolicy AllSigned
Set-ExecutionPolicy Bypass -Scope Process
Kodu çalıştıtıp yukarıdaki sonucu elde ettikten sonra redisi kurmak için aşağıdaki komutu çalıştırın.
Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
Bu kod chocolatey.org sitesindeki PowerShell Scriptini kullanarak redis server kurulumunu yapar.
Chocolatey (choco.exe) is now ready. Bilgisini aldıktan sonra Chocolatey Paket Yöneticimiz hazır. Şimdi aşağıdaki komut ile redis server'ı kuralım.
choco install redis-64 --version 3.0.503
Bu komutla birlikte redis server kurulumumuz tamamlandı.
https://community.chocolatey.org/packages/redis-64 sitede verilen komut; choco install redis-64 ile kurulum yaptığımda hata aldım. O yüzden versiyonlar kısmındaki bir önceki versiyonu kurarak ilerleyebildimi
Şimdi aşağıdaki komut ile redis server'ı ayağa kaldıralım
redis-server
Redis sever artık ayakta olduğuna göre şimdi diğer ayrıntılara girebiliriz.
Redis Veri Tipleri
Rediste In-MemoryCache'den farklı olarak veri tipleri vardır. Toplamda 5 adet veri tipi vardır, bunlar;
- Redis String
- Redis List
- Redis Set
- Redis Sorted Set
- Redis Hash
Şimdi bu veri tiplerini tek tek inceleyelim, bunun için bir proje yapmadan önce Redis CLI üzerinden incelemelerimize devam edeceğiz. Redis CLI'ı aya kaldırmak için yine Power Shell ekranına aşağıdaki kodu yazmalıyız.
redis-cli
Bu komutu yazdıktan sonra redisin hangi port üzerinden çalıştığını görebiliriz.
Redis 6373 portunda ayakta.
Redis String
Redis Sting veri tipi key value mantığında çalışan bir veri tipidir. ByteArray'e Serilaze edip istediğiniz nesneyi saklayabilirsiniz. CLI ile örnek kullanımı inceleyecek olursak;
SET komutu ile belleğe bir değer atayabiliyoruz;
SET SiteAddress ekremozer.com
Yukarıdaki komutta SiteAddress keyimiz ekremozer.com ise value'muz oluyor. Bu komutu çalıştırdıktan sonra OK yanıtını aldıysak işlem tamadır.
GET komutu ile de ramdan nesneyi okuyabiliyoruz.
GET SiteAddress
GETRANGE komutuyla verdiğimiz index aralığını okuyoruz.
GETRANGE SiteAddress 0 8
0. indexten başlayıp 8. indexe kadar olan kısmı okuyor.
INCR komutu integer tipindeki değeri +1 arttırır. Örnek olarak UnitInStock keyinde bir değer atayıp arttıralım.
SET UnitInStock 10
GET UnitInStock
INCR UnitInStock
Yukarıda gördüğünüz gibi UnitInStock keyinde değeri 10 olan bir string ifadeyi cache attım, GET komutuyla kontrol ettim ve son olarak INCR komutuyla 1 arttırdım. Gelen sonuç (integer) 11 oldu.
Eğer bu değeri 1'er olarak değilde verdiğim değere göre arttırmak istediğimde ise INCRBY komutunu kullanmam gerek;
INCRBY UnitInStock 5
INCRBY yazıp Key'imi yazdıktan sonra arttırmak istediğim sayıyı giriyorum. Yukarıda görüldüğü gibi UnitInStock 11'den 16 ya çıktı.
Son olarakta değeri azaltmak içinde DECR ve DECRBY komutlarını kullanmamız gerekiyor.
DECR UnitInStock
DECRBY UnitInStock 5
Yukarıda görüldüğü gibi UnitInStock değerim DECR komutuyla 16'dan 15'e ve DECRBY komutuyla verdiğim 5 değeri ile 15'den 10'a düştü.
APPEND komutu string ifadelerin sonuna vereceğimiz değeri eklemek için kullanılır;
APPEND SiteAddress /hakkimda
APPEND komutuyla ekremozer.com olan SiteAddress değerimiz ekremozer.com/hakkimda olarak güncellendiğini GET komutuyla görebiliyoruz. Redis CLI'da kullanabileceğimiz daha bir çok komut var ancak hepsine değinemeyeceğim.
Redis List
Bu veritipinde isminden anlaşılabileceği gibi list olarak verileri saklayabiliyoruz. Şimdi Redis CLI'ı Power Shell üzerinden açıp bir kaç örnekle inceleyelim.
LPUSH komutuyla bir dizin oluşturabilir veya mevcut dizine ekleme yapabiliriz. Örneğin Visitors adında bir dizemiz olsun.
LPUSH Visitors Ekrem
Yukarıdaki komut Visitors adında bir dize oluşturup Ekrem değerini içine atıyor.
Şimdi dizeye bir ekleme daha yapalım;
LPUSH Visitors Hakan
LPUSH komutu dizeye baştan ekleme yapar yani LEFT PUSH mantığında çalışır. Şimdi dizemizdeki verileri LRANGE komutu ile listeleyelim.
LRANGE Visitors 0 -1
LRANGE komutu başlangıç ve bitiş indexi olarak iki tane parametre alır, eğer -1 yazarsak tüm dizeyi getirir.
Gördüğünüz gibi ilk olarak Ekrem değerini eklemiştim ancak LPUSH ile yaptığım için Hakan değeri dizenin başına gelerek ilk sıraya geldi. RPUSH komutuyla bir değer eklediğimde ise dizenin sonuna gelecektir.
127.0.0.1:6379> RPUSH Visitors Ozer
LRANGE Visitors 0 -1
Yukarıdaki komutla dizenin sonuna Ozer değerini attım, ve dizenin tüm değerlerini çektiğim zaman sonuç aşağıdaki gibi oluyor;
Dizeden sadece belirttiğim index'e ait elemanı çekmek içinse LINDEX komutunu kullanıyorum.
LINDEX Visitors 1
Yukarıdaki komutu çalıştırdığım zaman 1. indexe ait olan Ekrem değerini bana dönüyor.
Listeden veri silmek için LPOP ve RPOP komutlarını kullanmamız gerekiyor. LPOP baştan, RPOP ise sondan olacak şekilde verileri siler.
LPOP Visitors
RPOP Visitors
LRANGE Visitors 0 -1
Yukarıdaki komutları sırasıyla çalıştırdığım zaman, dizenin ilk elemanı Hakan ve son elemanı Ozer değerlerini silip dizede sadece Ekrem değerini bıraktı.
Redis Set
Set veri tipi aynı List tipindeki gibi içinde dize şeklinde veri tutar. List tipinden farkları ise birinci olarak içindeki değerler unique olmak zorunda, ikincisi ise dizeye eklenen verilerin ekleme sıralaması random şekilde olmaktadır. Listteki gibi başına veya sonuna veri ekleyebilme tercihi bizde değildir.
SADD komutuyla dize eleman ekliyoruz;
SADD MenuItems HomePage
MetuItems dizemine HomePage değerinde bir eleman ekledim. Dönen (integer) 1 cevabından anlaşıldığı üzere dizeye 1 adet nesne ekledi. Aynı komutu tekrar çalıştırdığımda ise;
(integer) 0 cevabını alıyorum, yani HomePage değeri MenuItems dizesinde olduğu için dizeye ekleme işlemi yapmadı. Şimdi dizeye bir kaç tane daha eleman ekleyelim;
SADD MenuItems Blog SADD MenuItems Contact SADD MenuItems Search
Dizeye benzersiz 3 elaman daha ekledim ve hepsi eklendi. SMEMBERS komutu ile dizeyi listeleyebiliyorum;
SMEMBERS MenuItems
Yukarıdaki çıktı da görüldüğü üzere dizedeki elemanlar benim ekleme sırama göre değilde rastgele sıralanmış şekildedir. Dizeden bir değer silmek istediğimde ise SREM komutunu kullanmam gerekiyor.
SREM MenuItems Search
Search değerini SREM komutuyla silip dizeyi tekrar listelediğimde elemanın dizeden silindiğini görüyoruz.
SortedSet
SortedSet veri tipi Set tipinden farklı olarak sıralamasına bizim karar verebildiğimiz bir veri tipidir, Eklenecek verinin sırasını SCORE parametresiyle belirleyebiliyoruz;
ZADD Categories 1 NetCore ZADD Categories 4 Sql ZADD Categories 2 NetMvc ZADD Categories 3 AspNet
ZADD komutundan sonra Dizemin adını, sonra Score değerini ve son olarakta dizeye ekleyeceğim değeri yazdım.
Yukarıda görüldüğü gibi tüm değerler dizeye eklendi, sıralamak istediğimde ise, ZRANGE komutunu kullanıyorum;
ZRANGE Categories 0 -1
Dönen sonuçta görüldüğü gibi rastgele veya ekleme sırama göre değilde verdiğim Score değerine göre ekleniyor. SortedSet veri tipinde değerler uniqe olmak zorunda ancak Score değerleri tekrar eden olabilir, yani score değeri 3 olan iki tane değerim olabilir. Verileri score değeriyle listelemen için WITHSCORES parametresini komutun sonuna ekliyorum.
ZRANGE Categories 0 -1 WITHSCORES
Bu komutla beraber her değerin bir alt satırına Score değeri de gelmiş oluyor. Dizeden bir değeri silmek içinse ZREM komutunu kullanıyoruz.
ZREM Categories AspNet
Dizeyi tekrar listelediğimde AspNet değerinin silindiğini görüyoruz.
Hash
Hash veri tipinde verileri key value olarak saklayabiliyoruz, örneğin ContactList adında bir verimizde isim ve mailleri saklayabiliriz. Verileri HMSET komutuyla ekliyoruz.
HMSET ContactList Ekrem mail@ekremozer.com HMSET ContactList Hakan hakan@ekremozer.com
ContactList adındaki Hash veri setime 2 adet değer ekledim. Verileri tek tek okumak için HGET komutunu kullanıyoruz.
HGET ContactList Ekrem
Görüldüğü gibi Ekrem keyine karşılık gelen value mail@ekremozer.com sonucu döndü. Tamamını listelemek içinse HGETALL komutunu kullanıyoruz.
HGETALL ContactList
ContactListe ait tüm değerler Key Value şeklinde listelendi. Dizeden değer silmek içinse HDEL komutunu kullanıyoruz.
HDEL ContactList Hakan
HDEL komutuyla dizeden Hakan keyini silip listelediğimde dönen sonuçta silinmiş olduğunu görüyoruz.
Redisteki veri tipleri bukadardı ancak komutlar bu kadar değil. Burada sadece bir kısmına değindik. Tüm komutları incelemek için https://redis.io/commands adresini ziyaret edebilirsiniz.
Redisin kurulumunu ve veri tiplerini anlatmaya çalıştım. Şimdi asıl konumuz olan .net core üzerinde redis yapısını nasıl kullanacağımıza bakalım.
StackExchange.Redis API
Yukarıda CLI üzerinden yazdığımız komutların tamamını .net core üzerinde kullanabilmek için StackExchange.Redis kütüpanesinden yararlanacağız. Öncelikle Nuget Package Manager üzerinden kütüpanemizi uygulamamıza ekleyelim.
StackExchange.Redis
Ya da Package Console üzerrinden aşağıdaki komut ile ekleyebiliriz
Install-Package StackExchange.Redis
Nuget'ı uygulamamıza ekledikten sonra apiyi kullanabilmemiz için PowerShell üzerinden redisi ayağa kaldırıyoruz ve redis-cli komutu ile serverin adresini alıyoruz.
Redis Server Url'imiz 127.0.1.1:6379 Ip ve Portunda ayakta. Bu adrese uygulamamdan erişebilmek için appsettings.json'a ekliyorum.
"RedisServerUrl": "127.0.0.1:6379"
Artık Redis için classımı yazabilirim. RedisStackExchangeCacheManager isminde bir class oluşturuyoruz.
namespace NetCoreRedis.Web.Core.Caching { public class RedisStackExchangeCacheManager { private readonly ConnectionMultiplexer _redisConnector; public RedisStackExchangeCacheManager(IConfiguration configuration) { var redisServerUrl = configuration["RedisServerUrl"]; _redisConnector = ConnectionMultiplexer.Connect(redisServerUrl); } public IDatabase GetDb(int dbIndex = -1) { return _redisConnector.GetDatabase(dbIndex); } } }
Redis ile iletişim kurmak için ConnectionMultiplexer tipinde readonly bir property tanımlıyorum. Sonra Consturctor metodumu içinde appsettingse erişebilmem için IConfiguration tipinde bir parametre alacak şekilde oluşturuyorum. Metodun içinde RedisServerUrl'i aldıktan sonra aşağıdaki kod satırıyla _redisConnector değişkenimi redis server'a bağlanıyorum.
_redisConnector = ConnectionMultiplexer.Connect(redisServerUrl);
Bu classımda IDatabase dönen ve dbIndex parametresi alan tek bir metodum olacak. Redisin varsayılan olarak 16 tane db'si geliyor. 0 ile 15 arası indexleri vererek istediğimiz dbye erişebiliyoruz. GetDatabase() metodunu boş bırakırsak veya -1 verirsen default olarak ilk dbyi bize dönecektir. Bu classımızdaki işlemleri tamamladık. Son olarak classımızı servis olarak Starup.cs'e ekleyelim.
public void ConfigureServices(IServiceCollection services) { services.AddSingleton<RedisStackExchangeCacheManager>(); }
Classımızı AddSingleton olarak ekledik, uygulama ayağa kalktığında nesnenin bir örneğini alacak, bu işlemi yaparkende constructor metoda girip redis server ile bağlantı kuracak. Artık biz nesneyi her çağırdığımızda redis server'a connect olmuş bir classımıza erişmiş olacağız. Şimdi redisi kullanmamız için her şey hazır, yukarıda bahsettiğim 6 veri tipine ait yaptığımız işlemleri şimdi .net core üzerinde yapalım. Ayrı ayrı incelemeniz için her bir veri tipini bir action da örneklendireceğim.
Öncelikle HomeController'da IDatabase _redisDb tipinde bir değişken oluşturup consructor metodunda redis classımı parametre olarak çağırıp GetDB() metoduyla yukarıda oluşturduğum değişkeni dolduruyorum. Artık _redisDb değişkenimle redis server ile iletişim kurabilirim.
private readonly IDatabase _redisDb; public HomeController(RedisStackExchangeCacheManager redisStackExchangeCacheManager) { _redisDb = redisStackExchangeCacheManager.GetDb(); }
Redis String Kullanımı
Redis String veri tipinin .net core ile kullanım örnekleri.
public IActionResult RedisString() { //SET SiteAddress ekremozer.com _redisDb.StringSet("SiteAddress", "ekremozer.com"); //ExpireTime Vermek için... _redisDb.StringSet("SiteAddress", "ekremozer.com", TimeSpan.FromMinutes(15)); //GET SiteAddress var siteAddress = _redisDb.StringGet("SiteAddress"); //GETRANGE SiteAddress 0 8 var siteAddressRange = _redisDb.StringGetRange("SiteAddress", 0, 8); if (siteAddress.HasValue) { //Cache'de değer varsa... } if (siteAddress.IsNullOrEmpty) { //Değer null veya empty ise... } if (siteAddress.IsInteger) { //Değer integer tipindeyse... } if (siteAddress.IsNull) { //Değer null ise... } //Değeri stringe parse etmek için... var siteAddressValue = siteAddress.ToString(); var siteAddressRangeValue = siteAddressRange.ToString(); //SET UnitInStock 10 _redisDb.StringSet("UnitInStock", 10); var unitInStock = _redisDb.StringGet("UnitInStock").ToString(); //INCR UnitInStock _redisDb.StringIncrement("UnitInStock"); unitInStock = _redisDb.StringGet("UnitInStock").ToString(); //INCRBY UnitInStock 5 _redisDb.StringIncrement("UnitInStock", 5); unitInStock = _redisDb.StringGet("UnitInStock").ToString(); //DECR UnitInStock _redisDb.StringDecrement("UnitInStock"); unitInStock = _redisDb.StringGet("UnitInStock").ToString(); //DECRBY UnitInStock 5 _redisDb.StringDecrement("UnitInStock", 5); unitInStock = _redisDb.StringGet("UnitInStock").ToString(); //APPEND SiteAddress /hakkimda _redisDb.StringAppend("SiteAddress", "/hakkimda"); siteAddressValue = _redisDb.StringGet("SiteAddress").ToString(); return Content("Redis String"); }
Redis List Kullanımı
Redis List veri tipinin .net core ile kullanım örnekleri.
public IActionResult RedisList() { //LPUSH Visitors Ekrem _redisDb.ListLeftPush("Visitors", "Ekrem"); //Dizin olarak veri eklemek için... _redisDb.ListLeftPush("Visitors", new RedisValue[] { "Ekrem", "Hakan" }); //ExpireTime Vermek için... _redisDb.KeyExpire("Visitors", TimeSpan.FromMinutes(15)); //LRANGE Visitors 0 -1 if (_redisDb.KeyExists("Visitors"))//Cache'de bu key'e ait veri varsa { var visitors = _redisDb.ListRange("Visitors"); //RedisValue array'i list stringe parse etmek için... var stringVisitors = visitors.Select(item => item.ToString()).ToList(); } //RPUSH Visitors Ozer _redisDb.ListRightPush("Visitors", "Ozer"); var visitorsRightPush = _redisDb.ListRange("Visitors"); //LINDEX Visitors 1 var visitorByIndex = _redisDb.ListGetByIndex("Visitors", 1); //LPOP Visitors _redisDb.ListLeftPop("Visitors"); var visitorsLeftPop = _redisDb.ListRange("Visitors"); //RPOP Visitors _redisDb.ListRightPop("Visitors"); var visitorsRightPop = _redisDb.ListRange("Visitors"); //Listeden value'ye göre eleman silmek için... _redisDb.ListRemove("Visitors", "Ozer"); var visitorsRemove = _redisDb.ListRange("Visitors"); return Content("Redis List"); }
Redis Set Kullanımı
Redis Set veri tipinin .net core ile kullanım örnekleri.
public IActionResult RedisSet() { //SADD MenuItems HomePage _redisDb.SetAdd("MenuItems", "HomePage"); //Dizin olarak veri eklemek için... _redisDb.SetAdd("MenuItems", new RedisValue[] { "Blog", "Contact", "Search" }); //ExpireTime Vermek için... _redisDb.KeyExpire("MenuItems", TimeSpan.FromMinutes(15)); //SMEMBERS MenuItems if (_redisDb.KeyExists("MenuItems"))//Cache'de bu key'e ait veri varsa { var menuItems = _redisDb.SetMembers("MenuItems"); //ListString türüne parse etmek için var stringMenuItems = menuItems.Select(item => item.ToString()).ToList(); //HashSet türüne parse etmek için var hashSetMenuItems = new HashSet<string>(); foreach (var item in menuItems) { hashSetMenuItems.Add(item.ToString()); } } //SREM MenuItems Search _redisDb.SetRemove("MenuItems", "Search"); var menuItemsRemove = _redisDb.SetMembers("MenuItems"); return Content("Redis Set"); }
Redis Sorted Set Kullanımı
Redis Sorted Set veri tipinin .net core ile kullanım örnekleri.
public IActionResult RedisSortedSet() { //ZADD Categories 1 NetCore _redisDb.SortedSetAdd("Categories", "NetCore", 1); //ExpireTime Vermek için... _redisDb.KeyExpire("Categories", TimeSpan.FromMinutes(15)); //Dizin olarak veri eklemek için var array = new[] { new SortedSetEntry("Sql", 4), new SortedSetEntry("NetMvc", 2), new SortedSetEntry("AspNet", 3) }; _redisDb.SortedSetAdd("Categories", array); //ZRANGE Categories 0 -1 //ZRANGE Categories 0 -1 WITHSCORES if (_redisDb.KeyExists("Categories"))//Cache'de bu key'e ait veri varsa { var categories = _redisDb.SortedSetScan("Categories").ToList(); //Key ve value'yu ayrı ayrı okumak için foreach (var item in categories) { var key = item.Key.ToString(); var value = item.Value; } //Aşağıda değerler score ile birlikte Sql: 4 şeklide gelecektir. //ListString türüne parse etmek için var stringCategories = categories.Select(item => item.ToString()).ToList(); //HashSet türüne parse etmek için var hashSetCategories = new HashSet<string>(); foreach (var item in categories) { hashSetCategories.Add(item.ToString()); } //Küçükten büyüğe sıralama var orderByAscending = _redisDb.SortedSetRangeByRank("Categories", order: Order.Ascending); //Büyükten küçüğe sıralama var orderByDescending = _redisDb.SortedSetRangeByRank("Categories", order: Order.Descending); //Başlangıç ve bitiş indexine göre okuma var categoriesWithRange = _redisDb.SortedSetRangeByRank("Categories", 0, 2); } //ZREM Categories AspNet _redisDb.SortedSetRemove("Categories", "AspNet"); var categoriesRemove = _redisDb.SortedSetScan("Categories").ToList(); return Content("Redis Sorted Set"); }
Redis Hash Kullanımı
Redis Hash veri tipinin .net core ile kullanım örnekleri.
public IActionResult RedisHash() { //HMSET ContactList Ekrem mail@ekremozer.com _redisDb.HashSet("ContactList", "Ekrem", "mail@ekremozer.com"); //Dizin olarak veri eklemek için var array = new[] { new HashEntry("Hakan", "hakan@ekremozer.com"), new HashEntry("Info","info@ekremozer.com"), }; _redisDb.HashSet("ContactList", array); //ExpireTime Vermek için... _redisDb.KeyExpire("ContactList", TimeSpan.FromMinutes(15)); if (_redisDb.KeyExists("ContactList"))//Cache'de bu key'e ait veri varsa { //HGET ContactList Ekrem var contactListItem = _redisDb.HashGet("ContactList", "Ekrem"); if (contactListItem.HasValue) { //Cache'de değer varsa... } if (contactListItem.IsNullOrEmpty) { //Değer null veya empty ise... } if (contactListItem.IsInteger) { //Değer integer tipindeyse... } if (contactListItem.IsNull) { //Değer null ise... } //Değeri stringe parse etmek için... var contactListItemString = contactListItem.ToString(); //HGETALL ContactList var contactList = _redisDb.HashGetAll("ContactList"); //Dictionary<string,string> türüne parse etmek için... var contactListDictionary = contactList.ToDictionary<HashEntry, string, string>(item => item.Key, item => item.Value); //HDEL ContactList Hakan _redisDb.HashDelete("ContactList", "Hakan "); var contactListDel = _redisDb.HashGetAll("ContactList"); } return Content("Redis Hash"); }
Redis IDistributed Cache Kullanımı
Redisin bir diğer kullanım yönetimi de IDistributed interface'ini kullanmaktır. Bu .net core daki IMemoryCache mantığına çok yakındır ancak faklı olarak sadece string ve byte array veri tipini cache'de tutuyor. Yine de serialize işlemi uygulayarak complex typeları ve fiziksel dosyalarımızı da cache de tutabiliriz.
Öncelikle IDistributed interface'ini kullanmak için aşağıdaki kütüphaneyi nuget package yöneticisinden projemize ekleyelim.
Nuget Package Manager üzerinden eklemek için;
Microsoft.Extensions.Caching.StackExchangeRedis
Nuget Package Console üzerinden eklemek için;
Install-Package Microsoft.Extensions.Caching.StackExchangeRedis
Kütüphaneyi projemize ekledikten sonra redisi Startup.cs de servis olarak ekliyoruz.
public void ConfigureServices(IServiceCollection services) { var redisServerUrl = Configuration["RedisServerUrl"]; services.AddStackExchangeRedisCache(options => { options.Configuration = redisServerUrl; }); }
Daha önceden appsettings.json'da tanımladığım RedisServerUrli çekiyorm ve AddStackExchangeRedisCache metoduyla projeme servis olarak ekliyorum. Artık redis IDistributed projemizde kullanıma hazır. Cache yönetimi için best practics olaması açısından ayrı bir interface ve class yapısı kuracağım. Bu classtaki metodlarımda kullacağım CacheKey adında bir class oluşturuyorum.
namespace NetCoreRedis.Web.Core.Caching { public class CacheKey { #region Ctor public CacheKey(string key, int cacheTime) { Key = key; CacheTime = cacheTime; } public CacheKey(string key, int cacheTime, int cacheSlidingTime) { Key = key; CacheTime = cacheTime; CacheSlidingTime = cacheSlidingTime; } #endregion public string Key { get; protected set; } public int CacheTime { get; set; } public int? CacheSlidingTime { get; set; } } }
Classım objeleri cache atarken ihtiyaç duyacağım key, CacheTime ve null geçile bilen CacheSlidingTime filedleri içeriyor. Bu parametleri instance alırken vermek için iki farklı constructure metodu da classımın içinde oluşturdum.
CacheKey classım hazır şimdide ihtiyaç duyabileceğimi düşündüğüm tüm metodlar için projeme bir ICacheManager adında interface ekliyorum.
namespace NetCoreRedis.Web.Core.Caching { public interface ICacheManager { T Get<T>(string key); Task<T> GetAsync<T>(string key); T GetOrCreate<T>(CacheKey cacheKey, Func<T> acquire); Task<T> GetOrCreateAsync<T>(CacheKey cacheKey, Func<T> acquire); byte[] Get(string key); Task<byte[]> GetAsync(string key); byte[] GetOrCreate(CacheKey cacheKey, Func<byte[]> acquire); Task<byte[]> GetOrCreateAsync(CacheKey cacheKey, Func<byte[]> acquire); string GetString(string key); Task<string> GetStringAsync(string key); string GetOrCreateString(CacheKey cacheKey, Func<string> acquire); Task<string> GetOrCreateStringAsync(CacheKey cacheKey, Func<string> acquire); void Set(CacheKey cacheKey, object model); Task SetAsync(CacheKey cacheKey, object model); void Set(CacheKey cacheKey, byte[] byteArray); Task SetAsync(CacheKey cacheKey, byte[] byteArray); void SetString(CacheKey cacheKey, string value); Task SetStringAsync(CacheKey cacheKey, string value); void Remove(string key); Task RemoveAsync(string key); } }
Metodların ne yaptığını kısaca açıklayayım;
T Get<T>(string key) string tipinde key parametresi alır vereceğiniz generic tipte model döner.
Task<T> GetAsync<T>(string key) üstteki metodun asenkron hali.
T GetOrCreate<T>(CacheKey cacheKey, Func<T> acquire) CacheKey tipinde ve vereceğiniz generic tipte değer dönen fonksiyon parametresi alır, değer cache'de varsa döner, yoksa oluşturup cache atıp nesneyi öyle döner.
Task<T> GetOrCreateAsync<T>(CacheKey cacheKey, Func<T> acquire) üstteki metodun asenkron hali.
byte[] Get(string key) string tipinde key alır ve byte array tipinde obje döner.
Task<byte[]> GetAsync(string key) üstteki metodun asenkron hali.
byte[] GetOrCreate(CacheKey cacheKey, Func<byte[]> acquire) CacheKey tipinde ve vereceğiniz byte array tipte değer dönen fonksiyon parametresi alır, değer cache'de varsa döner, yoksa oluşturup cache atıp nesneyi öyle döner.
Task<byte[]> GetOrCreateAsync(CacheKey cacheKey, Func<byte[]> acquire) üstteki metodun asenkron hali.
string GetString(string key) string tipinde key alır ve cacheden string değer döner.
Task<string> GetStringAsync(string key) üstteki metodun asenkron hali.
string GetOrCreateString(CacheKey cacheKey, Func<string> acquire) CacheKey tipinde ve vereceğiniz string tipte değer dönen fonksiyon parametresi alır, değer cache'de varsa döner, yoksa oluşturup cache atıp nesneyi öyle döner.
Task<string> GetOrCreateStringAsync(CacheKey cacheKey, Func<string> acquire) üstteki metodun asenkron hali.
void Set(CacheKey cacheKey, object model) CacheKey tipinde değer alır ve vereceğiniz object tipindeki nesneyi cache atar.
Task SetAsync(CacheKey cacheKey, object model) üstteki metodun asenkron hali.
void Set(CacheKey cacheKey, byte[] byteArray) CacheKey tipinde değer alır ve vereceğiniz byte array tipindeki nesneyi cache atar.
Task SetAsync(CacheKey cacheKey, byte[] byteArray) üstteki metodun asenkron hali.
void SetString(CacheKey cacheKey, string value) CacheKey tipinde değer alır ve vereceğiniz string tipindeki nesneyi cache atar.
Task SetStringAsync(CacheKey cacheKey, string value) üstteki metodun asenkron hali.
void Remove(string key) vereceğiniz key'e ait objeyi cache'den siler.
Task RemoveAsync(string key) üstteki metodun asenkron hali.
Şimdi bu interface'imden türeteceğim RedisIDistributedCacheManager adında bir class oluşturuyorum. Classıma IDistributedCache tipinde bir field ekliyorum ve constructor metodda bu fieldi dolduruyorum. Classımı ICacheManger interfaceinden türettiğim için startup'da bu tanımlamayı yapıyorum.
public void ConfigureServices(IServiceCollection services) { var redisServerUrl = Configuration["RedisServerUrl"]; services.AddStackExchangeRedisCache(options => { options.Configuration = redisServerUrl; }); services.AddSingleton<ICacheManager, RedisIDistributedCacheManager>(); }
private readonly IDistributedCache _distributedCache; public RedisIDistributedCacheManager(IDistributedCache distributedCache) { _distributedCache = distributedCache; }
Redis server ile konuşacağımız _distributedCache değişkenimiz artık hazır. Şimdi metodladımızı inceleyelim. Öncelikle redis byte array ve string tipinde verileri cachlediği için biz complex typeları cachlerken byte arraya parse edeceğiz. Bunun için byte arrayden modele, modelden byte arraya parse işlemi yapan 2 tane metod ekliyorum classımın altına.
ModelToByteArray generic türde nesne alıyor ve önce Json'a Serialize edip sonrada byte array'e dönüştürüp bize nesneyi byte array olarak dönüyor.
ByteArrayToModel byte array parametresi alıyor ve byte arrayı önce stringe, sonra de verdiğimiz generic tipe parse edip bize modeli dönüyor.
private static byte[] ModelToByteArray<T>(T model) { if (model == null) { return null; } var jsonModel = JsonConvert.SerializeObject(model); var byteArray = Encoding.UTF8.GetBytes(jsonModel); return byteArray; } private static T ByteArrayToModel<T>(byte[] byteArray) { if (byteArray == null) { return default; } var jsonModel = Encoding.UTF8.GetString(byteArray); var model = JsonConvert.DeserializeObject<T>(jsonModel); return model; }
Sonrasında yine CacheKey parametresini rediste kullanmak için DistributedCacheEntryOptions tipine prepare eden metodu yazıyruz.
private static DistributedCacheEntryOptions PrepareDistributedCacheEntryOptions(CacheKey cacheKey) { var distributedCacheEntryOptions = new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(cacheKey.CacheTime) }; if (cacheKey.CacheSlidingTime > 0) { distributedCacheEntryOptions.SlidingExpiration = TimeSpan.FromMinutes((int)cacheKey.CacheSlidingTime); } return distributedCacheEntryOptions; }
T Get<T> ve Task<T> GetAsync<T> metodları;
public T Get<T>(string key) { var byteArray = _distributedCache.Get(key); var model = ByteArrayToModel<T>(byteArray); return model; } public async Task<T> GetAsync<T>(string key) { var byteArray = await _distributedCache.GetAsync(key); var model = ByteArrayToModel<T>(byteArray); return model; }
Strin olarak key parametresi alıp cacheden byte array olarak veriyi çektikten sonra verdiğimiz generic type'a parse edip nesneyi dönüyor.
T GetOrCreate<T>(CacheKey cacheKey, Func<T> acquire) ve Task<T> GetOrCreateAsync<T>(CacheKey cacheKey, Func<T> acquire) metodu;
public T GetOrCreate<T>(CacheKey cacheKey, Func<T> acquire) { if (cacheKey.CacheTime <= 0 && (cacheKey.CacheSlidingTime == null || cacheKey.CacheSlidingTime <= 0)) { return acquire(); } var cacheByteArray = _distributedCache.Get(cacheKey.Key); if (cacheByteArray == null) { var model = acquire(); if (model != null) { Set(cacheKey, model); } return model; } var cacheModel = ByteArrayToModel<T>(cacheByteArray); return cacheModel; } public async Task<T> GetOrCreateAsync<T>(CacheKey cacheKey, Func<T> acquire) { if (cacheKey.CacheTime <= 0 && (cacheKey.CacheSlidingTime == null || cacheKey.CacheSlidingTime <= 0)) { return acquire(); } var cacheByteArray = await _distributedCache.GetAsync(cacheKey.Key); if (cacheByteArray == null) { var model = acquire(); if (model != null) { await SetAsync(cacheKey, model); } return model; } var cacheModel = ByteArrayToModel<T>(cacheByteArray); return cacheModel; }
CacheKey parametresinde CacheTime belirtilmemişse süresiz bir cacheleme yapmaz ve dinamik fonsiyonu çağırır nesneyi canlıdan döner.
byte[] Get(string key) ve Task<byte[]> GetAsync(string key) metodu;
public byte[] Get(string key) { var byteArray = _distributedCache.Get(key); return byteArray; } public async Task<byte[]> GetAsync(string key) { var byteArray = await _distributedCache.GetAsync(key); return byteArray; }
byte[] GetOrCreate(CacheKey cacheKey, Func<byte[]> acquire) ve Task<byte[]> GetOrCreateAsync(CacheKey cacheKey, Func<byte[]> acquire) metodları;
public byte[] GetOrCreate(CacheKey cacheKey, Func<byte[]> acquire) { if (cacheKey.CacheTime <= 0 && (cacheKey.CacheSlidingTime == null || cacheKey.CacheSlidingTime <= 0)) { return acquire(); } var cacheByteArray = _distributedCache.Get(cacheKey.Key); if (cacheByteArray == null) { var byteArray = acquire(); if (byteArray != null) { Set(cacheKey, byteArray); } return byteArray; } return cacheByteArray; } public async Task<byte[]> GetOrCreateAsync(CacheKey cacheKey, Func<byte[]> acquire) { if (cacheKey.CacheTime <= 0 && (cacheKey.CacheSlidingTime == null || cacheKey.CacheSlidingTime <= 0)) { return acquire(); } var cacheByteArray = await _distributedCache.GetAsync(cacheKey.Key); if (cacheByteArray == null) { var byteArray = acquire(); if (byteArray != null) { await SetAsync(cacheKey, byteArray); } return byteArray; } return cacheByteArray; }
string GetString(string key) ve Task<string> GetStringAsync(string key) metodları;
public string GetString(string key) { var value = _distributedCache.GetString(key); return value; } public async Task<string> GetStringAsync(string key) { var value = await _distributedCache.GetStringAsync(key); return value; }
Bu metodlarda byte[] dönen metodlar gibi doğrudan redis'in kendi metodlarını kullanarak veriyi döner.
string GetOrCreateString(CacheKey cacheKey, Func<string> acquire) ve Task<string> GetOrCreateStringAsync(CacheKey cacheKey, Func<string> acquire) metodları;
public string GetOrCreateString(CacheKey cacheKey, Func<string> acquire) { if (cacheKey.CacheTime <= 0 && (cacheKey.CacheSlidingTime == null || cacheKey.CacheSlidingTime <= 0)) { return acquire(); } var cacheValue = _distributedCache.GetString(cacheKey.Key); if (string.IsNullOrEmpty(cacheValue)) { var value = acquire(); if (string.IsNullOrEmpty(value)) { SetString(cacheKey, value); } return value; } return cacheValue; } public async Task<string> GetOrCreateStringAsync(CacheKey cacheKey, Func<string> acquire) { if (cacheKey.CacheTime <= 0 && (cacheKey.CacheSlidingTime == null || cacheKey.CacheSlidingTime <= 0)) { return acquire(); } var cacheValue = await _distributedCache.GetStringAsync(cacheKey.Key); if (string.IsNullOrEmpty(cacheValue)) { var value = acquire(); if (string.IsNullOrEmpty(value)) { await SetStringAsync(cacheKey, value); } return value; } return cacheValue; }
void Set(CacheKey cacheKey, object model) ve Task SetAsync(CacheKey cacheKey, object model) metodları;
public void Set(CacheKey cacheKey, object model) { var distributedCacheEntryOptions = PrepareDistributedCacheEntryOptions(cacheKey); var byteArray = ModelToByteArray(model); _distributedCache.Set(cacheKey.Key, byteArray, distributedCacheEntryOptions); } public Task SetAsync(CacheKey cacheKey, object model) { var memoryCacheEntryOptions = PrepareDistributedCacheEntryOptions(cacheKey); var byteArray = ModelToByteArray(model); _distributedCache.SetAsync(cacheKey.Key, byteArray, memoryCacheEntryOptions); return Task.CompletedTask; }
void Set(CacheKey cacheKey, byte[] byteArray) ve Task SetAsync(CacheKey cacheKey, byte[] byteArray) metotları;
public void Set(CacheKey cacheKey, byte[] byteArray) { var distributedCacheEntryOptions = PrepareDistributedCacheEntryOptions(cacheKey); _distributedCache.Set(cacheKey.Key, byteArray, distributedCacheEntryOptions); } public Task SetAsync(CacheKey cacheKey, byte[] byteArray) { var memoryCacheEntryOptions = PrepareDistributedCacheEntryOptions(cacheKey); _distributedCache.SetAsync(cacheKey.Key, byteArray, memoryCacheEntryOptions); return Task.CompletedTask; }
void SetString(CacheKey cacheKey, string value) ve Task SetStringAsync(CacheKey cacheKey, string value) metotları;
public void SetString(CacheKey cacheKey, string value) { var memoryCacheEntryOptions = PrepareDistributedCacheEntryOptions(cacheKey); _distributedCache.SetString(cacheKey.Key, value, memoryCacheEntryOptions); } public Task SetStringAsync(CacheKey cacheKey, string value) { var memoryCacheEntryOptions = PrepareDistributedCacheEntryOptions(cacheKey); _distributedCache.SetStringAsync(cacheKey.Key, value, memoryCacheEntryOptions); return Task.CompletedTask; }
void Remove(string key) ve Task RemoveAsync(string key) metodları;
public void Remove(string key) { _distributedCache.Remove(key); } public Task RemoveAsync(string key) { _distributedCache.Remove(key); return Task.CompletedTask; }
Şimdi HomeControllerda metodları örneklendirelim. HomeController'ın ICacheManager _cacheManager adında bir değişken tanımlayıp constructor metodunda dolduruyorum.
private readonly IDatabase _redisDb; private readonly ICacheManager _cacheManager; public HomeController(RedisStackExchangeCacheManager redisStackExchangeCacheManager, ICacheManager cacheManager) { _cacheManager = cacheManager; _redisDb = redisStackExchangeCacheManager.GetDb(); }
_cacheManager değişkenim ile bütün metodlara erişebilirim artık. DistributedCache adında bir action açıp tüm metodlar için birer örnek yapalım;
public async Task<IActionResult> DistributedCache() { var personals = FakeDataGenerator.GetPersonals(); var fileByteArray = FakeDataGenerator.GetFileByteArray(); //CacheKey oluşturma var cacheKey = new CacheKey("Personals", 15); //SlidingTime ile CacheKey oluşturma var cacheKeySlidingTime = new CacheKey("Image", 15, 3); //Nesneleri cachelemek için... _cacheManager.Set(cacheKey, personals); await _cacheManager.SetAsync(cacheKey, personals); //Fiziksel dosyaları byte[] a dönüştürüp cachlemek için _cacheManager.Set(cacheKeySlidingTime, fileByteArray); await _cacheManager.SetAsync(cacheKeySlidingTime, fileByteArray); //Cacheden nesne getirmek için var personalsCache = _cacheManager.Get<List<Personal>>("Personals"); var personalsCacheAsync = await _cacheManager.GetAsync<List<Personal>>("Personals"); var imageByteArray = _cacheManager.Get("Image"); var imageByteArrayAsync = _cacheManager.GetAsync("Image"); //Image dosyasını actionda dönemk için... //return File(imageByteArray, "image/png"); //Pdf dosyalarını actionda dönmek için... //return File(imageByteArray, "application/pdf"); //Nesneleri varsa cacheden getirmek yoksa, fonksiyon ile oluşturup cachlemek için... var personalsGetOrCreate = _cacheManager.GetOrCreate(cacheKey, () => FakeDataGenerator.GetPersonals()); var personalsGetOrCreateAsync =await _cacheManager.GetOrCreateAsync(cacheKey, FakeDataGenerator.GetPersonals);//Paremetre almayan metodları bu şekildede kullanabilirsiniz... //byte[] nesneleri varsa cacheden getirmek yoksa, fonksiyon ile oluşturup cachlemek için... var imageByteArrayGetOrCreate = _cacheManager.GetOrCreate(cacheKeySlidingTime, () => FakeDataGenerator.GetFileByteArray()); var imageByteArrayGetOrCreateAsync = await _cacheManager.GetOrCreateAsync(cacheKeySlidingTime, () => FakeDataGenerator.GetFileByteArray()); var cacheKeyString = new CacheKey("PersonalFullName", 15); var personalFullName = FakeDataGenerator.GetPersonalFullName(1); //String cachelemek için... _cacheManager.SetString(cacheKeyString, personalFullName); await _cacheManager.SetStringAsync(cacheKeyString, personalFullName); //String cacheden okumak için var personalFullNameCache = _cacheManager.GetString("PersonalFullName"); var personalFullNameCacheAsync = await _cacheManager.GetStringAsync("PersonalFullName"); //String nesneleri varsa cacheden getirmek yoksa, fonksiyon ile oluşturup cachlemek için... var personalFullNameGetOrCreate = _cacheManager.GetOrCreateString(cacheKeyString, () => FakeDataGenerator.GetPersonalFullName(1)); var personalFullNameGetOrCreateCacheAsync = await _cacheManager.GetOrCreateStringAsync(cacheKeyString, () => FakeDataGenerator.GetPersonalFullName(1)); //Cacheden veri silmek için _cacheManager.Remove("PersonalFullName"); await _cacheManager.RemoveAsync("PersonalFullName"); return Content("Redis IDistributed Cache"); }
Benim bu makalede anlatacaklarım bu kadar, umarım faydalı olmuşur. Projenin kaynak kodlarına bu linkten ulaşabilirsiniz: