ekrem özer

her yerde olan şeyler.

.Net Core Redis Kullanımı

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;

  1. Redis String
  2. Redis List
  3. Redis Set
  4. Redis Sorted Set
  5. 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: 

https://github.com/ekremozer/NetCoreRedis