.Net 6.0 ve Redis ile Cache Yönetimi
Merhaba, bu gönderide .net 6.0 ve redis ile cache yönetiminin nasıl yapılacağını örnek bir proje yaparak inceleyeceğiz ve redisle ilgili genel bilgilere bakacağız. Projenin linkine en aşağıdan ulaşabilirsiniz.
Redis Nedir?
Anahtar – Değer (Key-value) şeklindeki verileri sunucunun belleğinde (ram) tutan no-sql veritabanıdır.
Kullanım Alanları
Session Yönetimi, Pub/Sub, Önbellek (Cache) yönetimi, Rabbitmq benzeri kuyruk yönetimi (Rabbitmq için gönderi)
Adımlar
Redis Kurulumu
Redis ilk başlarda sadece linux üzerinde çalışsa da daha sonra ihtiyaçlar doğrultusunda windows sürümüne de çıktı. Şu link üzerinde redisi indirebilirsiniz İndirdikten sonra 64bit klasörü içerisindeki redis-server.exe‘yi çift tıklayıp redisi çalıştırın. Redis artık kullanılabilir durumda redis-cli.exe ile redisi test edebiliriz.
Rediste string, list, hashes gibi bazı tipler var. Üstteki örnek string içindi. list ve hash içinde ayrıca yazalım.
ilk satırda yazdığımız hmset komutu (hash multiset) json gibi string objelerini mapleyerek atama işlemi yapıyor. user:1 ise keyimiz oluyor
InMemoryCache
Bu yöntem ile veriler sadece host edilen web sunucunun belleğinde tutulur ve verilerinize sadece host ettiğiniz uygulama erişebilir ayrıca sunucunuz kapandığında ya da yeniden başladığında veriler silinir. Uygulamanın tek sunucuda olduğu durumlar için ideal bir çözümdür.
Distributed Caching
Birden çok uygulamanın erişebileceği bir yöntemdir. Redis gibi bir veritabanında veriler saklanır sunucu kapandığında veriler silinmez. Verilere kolayca erişmeden dolayı bakımı kolay olur. Session gibi ortak cacheler tutulabilir. Çok sunuculu uygulamalar için idela çözümdür.
.Net Entagrasyonu
.Net 6.0 Web api projesi açara redisin kullanımına bakacağız.
Redis ile ilgili nugette birçok paket var StackExchange.Redis en çok tercih edilen paketlerden aşağıdaki komut ile paketi yükleyelim
Install-Package StackExchange.Redis
appsettings.json dosyasına redisin bağlantı bilgilerini ekleyelim.
Default port 6379 olduğu için belirtmeye gerek yok.
Program.cs aşağıdaki eklemeleri yapalım
1 2 3 |
IConfiguration configuration = builder.Configuration; var multiplexer = ConnectionMultiplexer.Connect(configuration.GetConnectionString("Redis")); builder.Services.AddSingleton<IConnectionMultiplexer>(multiplexer); |
Services klasörü oluşturalım ve ICacheService Ekleyelim
1 2 3 4 5 6 7 8 9 |
public interface ICacheService { Task<string> GetValueAsync(string key); Task<bool> SetValueAsync(string key, string value); Task<T> GetOrAddAsync<T>(string key, Func<Task<T>> action) where T : class; T GetOrAdd<T>(string key, Func<T> action) where T : class; Task Clear(string key); void ClearAll(); } |
GetOrAdd fonksiyonu ile cache’den veri çektiğimizde veri null ise aynı zamanda set işlemini de yapıyoruz böylece kod tekrarı yapmıyoruz.
RedisCacheService adından bir class oluşturup ICacheService’den miras alalım.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
public class RedisCacheService : ICacheService { private readonly IConnectionMultiplexer _redisCon; private readonly IDatabase _cache; private TimeSpan ExpireTime => TimeSpan.FromDays(1); public RedisCacheService(IConnectionMultiplexer redisCon) { _redisCon = redisCon; _cache = redisCon.GetDatabase(); } public async Task Clear(string key) { await _cache.KeyDeleteAsync(key); } public void ClearAll() { var endpoints = _redisCon.GetEndPoints(true); foreach (var endpoint in endpoints) { var server = _redisCon.GetServer(endpoint); server.FlushAllDatabases(); } } public async Task<T> GetOrAddAsync<T>(string key, Func<Task<T>> action) where T : class { var result = await _cache.StringGetAsync(key); if (result.IsNull) { result = JsonSerializer.SerializeToUtf8Bytes(await action()); await SetValueAsync(key, result); } return JsonSerializer.Deserialize<T>(result); } public async Task<string> GetValueAsync(string key) { return await _cache.StringGetAsync(key); } public async Task<bool> SetValueAsync(string key, string value) { return await _cache.StringSetAsync(key,value, ExpireTime); } public T GetOrAdd<T>(string key, Func<T> action) where T : class { var result = _cache.StringGet(key); if (result.IsNull) { result = JsonSerializer.SerializeToUtf8Bytes(action()); _cache.StringSet(key, result,ExpireTime); } return JsonSerializer.Deserialize<T>(result); } } |
Bu servisde en çok kullanacağımız GetorAdd() fonksiyonu ana işlemlerimizi nerdeyse tek işlemde yapıyor.
Ayrıca 1 gün olarak da expire süresi de belirledik bu süre bitince key silinecek.
Cache değerini serilize ederken JsonSerializer.SerializeToUtf8Bytes kullanırsanız türkçe karekterler unicode olarak kaydedilir Serialize() de ise türkçe karakterleri parse edip kaydeder.
ICacheService’in bağımlılığını ekleyelim
1 |
builder.Services.AddSingleton<ICacheService, RedisCacheService>(); |
CacheController ekliyip bir endpoint üzerinden redise veri atıp çekelim
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
[ApiController] [Route("api")] public class CacheController : ControllerBase { private readonly ICacheService _cacheService; public CacheController(ICacheService cacheService) { _cacheService = cacheService; } [HttpPost("cache/{key}")] public async Task<IActionResult> Get(string key) { return Ok(await _cacheService.GetValueAsync(key)); } [HttpPost("cache")] public async Task<IActionResult> Post([FromBody] CacheRequestModel model) { await _cacheService.SetValueAsync(model.Key, model.Value); return Ok(); } [HttpDelete("cache/{key}")] public async Task<IActionResult> Delete(string key) { await _cacheService.Clear(key); return Ok(); } } |
CacheRequestModel.cs
1 2 3 4 5 |
public class CacheRequestModel { public string Key { get; set; } public string Value { get; set; } } |
Swagger ya da postman üzerinden testi gerçekleştirebiliriz. ayrıca yukarıda belirttiğim linkten redis-cli.exe yi de açıp verileri inceleyebiliriz.
Şimdi de GetOrAdd fonksiyonumuzu kullanalım. Örneğin bir kategorilerimiz olsun category/getall çağırdığımız zaman önce cache’den almayı deneyecek eğer yoksa sorgumuz çalışacak cache’e ekleyecek ve sorgu dönecek.
Category işlemlerimizi yapacak ICategoryService oluşturalım
1 2 3 4 |
public interface ICategoryService { List<CategoryModel> GetAllCategory(); } |
CategoryService.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public class CategoryService : ICategoryService { static List<CategoryModel> categories => new() { new CategoryModel { Id = 1, Name = "Electronic" }, new CategoryModel { Id = 2, Name = "Clothes" } }; public ICacheService CacheService { get; } public CategoryService(ICacheService cacheService) { CacheService = cacheService; } public List<CategoryModel> GetAllCategory() { return GetCategoriesFromCache(); } private List<CategoryModel> GetCategoriesFromCache() { return CacheService.GetOrAdd("allcategories", () => { return categories; }); } } |
GetOrAdd fonksiyonunda iki parametre var ilki keyin ismi (string) diğeri de bir delegate (Func<T>)
CategoryModel.cs
1 2 3 4 5 |
public class CategoryModel { public int Id { get; set; } public string Name { get; set; } } |
CategoryController ile de endpointimizi oluşturalım
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
[Route("api")] [ApiController] public class CategoryController : ControllerBase { private readonly ICategoryService _categoryService; public CategoryController(ICategoryService categoryService) { _categoryService = categoryService; } [HttpGet("category/getall")] public IActionResult GetAll() { return Ok(_categoryService.GetAllCategory()); } } |
Testimizi yapalım.
Data Consistency (Veri Tutarlılığı)
Verilerde tutarlılığı sağlamak için cache’de tutulan veri değiştiğinde ya direkt güncellemeliyiz ya da keyi silmeliyiz örneğin kategorilere bir ekleme yaptığımız zaman cache’deki veriyi güncellemezsek bir veri tutarsızlığı ortaya çıkar.
Proje linki : github.com/okankrdg/RedisSample
Bu gönderinin sonuna geldik Hoşçakalın 🙂