Distributed Caching Redis ile ASP.NET Core Web API

Bu makalemizde ASP.NET CORE Web Api ile Distributed Caching Redis'ten bahsedeceğiz.

Distributed Caching Redis ile ASP.NET Core Web API

ASP.NET Core'da Dağıtılmış Önbellekleme Nedir?

ASP.NET Core hem bellek içi uygulama tabanlı önbelleğe almayı hem de dağıtılmış önbelleğe almayı destekler. Uygulama içinde bulunan bellek içi önbelleğe almanın aksine, dağıtılmış önbelleğe alma uygulamanın dışındadır. Uygulamanın içinde bulunmaz ve aynı sunucuda bulunması gerekmez.

Dağıtılmış bir önbellek bir veya daha fazla uygulama, örnek veya sunucu tarafından paylaşılabilir, bu da uygulamanızın birden fazla örneğinin çalıştığı senaryolar için idealdir. Tüm örneklerin tutarlı verilere sahip olmasını sağlamaya yardımcı olur. Ancak, ağ gecikmesini en aza indirmek için dağıtılmış önbellek örneğinizi uygulamanıza mümkün olduğunca yakın yerleştirmeniz şiddetle tavsiye edilir.

Bellek içi önbellek gibi dağıtılmış önbellek de uygulamanızın yanıt süresini önemli ölçüde artırabilir. Ancak, dağıtılmış önbelleğin uygulanması uygulamaya özeldir, yani dağıtılmış önbelleği destekleyen birden fazla önbellek sağlayıcısı vardır.

En popüler dağıtık önbellek sağlayıcısı Redis'tir. Redis, çeşitli uygulamalar ve dağıtım senaryoları için uygun hale getiren sağlam özellikler ve ölçeklenebilirlik seçenekleri sunar.

Bu makalede, verileri bir Docker Container üzerinde yerel olarak çalışan bir Redis örneğine önbelleğe alan bir ASP.NET Core uygulaması oluşturmaya odaklanacağız.

Dağıtılmış Önbelleklemenin Artıları ve Eksileri

Artıları:

  1. Veri Tutarlılığı: Verilerin birden fazla sunucuda tutarlı olmasını sağlar.
  2. Paylaşılan Kaynak: Birden fazla uygulama veya sunucu, Redis gibi dağıtılmış bir önbelleğin tek bir örneğini kullanabilir ve uzun vadeli bakım maliyetlerini azaltır.
  3. Kalıcılık: Önbellek, harici olarak bulunduğu için sunucu yeniden başlatmaları ve uygulama dağıtımları boyunca bozulmadan kalır.
  4. Kaynak Optimizasyonu: Yerel sunucu kaynaklarını tüketmez, diğer uygulama süreçleri için korur.
  5. Ölçeklenebilirlik: Dağıtılmış önbellekler, büyük miktarda veriyi ve yüksek trafik hacimlerini işlemek için kolayca ölçeklenebilir.
  6. Hata Toleransı: Birçok dağıtık önbellekleme çözümü yerleşik hata toleransı ve replikasyon özellikleri sunarak sistem güvenilirliğini artırır.
  7. Gelişmiş Özellikler: Dağıtık önbellekler genellikle veri tahliye politikaları, zaman aşımı ve kümeleme gibi gelişmiş özelliklerle birlikte gelir.

Eksiler:

  1. Gecikme: Yanıt süreleri, dağıtılmış önbellek sunucusuna olan bağlantı gücüne bağlı olarak ağ gecikmesi nedeniyle bellek içi önbelleğe almaya kıyasla biraz daha yavaş olabilir.
  2. Karmaşıklık: Dağıtılmış bir önbelleğin kurulması ve yönetilmesi sistem mimarisine karmaşıklık katabilir.
  3. Maliyet: Özellikle yönetilen hizmetler veya daha büyük kurulumlar için dağıtılmış bir önbelleğin çalıştırılması ve bakımı ile ilgili ek maliyetler olabilir.
  4. Ağ Bağımlılığı: Performans, ağ güvenilirliğine ve hızına bağlıdır. Ağ sorunları önbellek performansını etkileyebilir.
  5. Güvenlik Endişeleri: Uygulamanız ve dağıtılmış önbellek arasında güvenli iletişim sağlamak için ek yapılandırmalar ve hususlar gerekir.
  6. Veri Senkronizasyonu: Oldukça dinamik ortamlarda, uygulama ve önbellek arasında veri senkronizasyonunu sağlamak zor olabilir.
  7. Bu kapsamlı liste, uygulamalarınızda dağıtık önbellek kullanmanın avantajları ve potansiyel dezavantajları hakkında dengeli bir görüş sağlar. Bildiğiniz gibi, Sistem Tasarımı bir ödünleşme oyunudur. Tek bir EN İYİ Yaklaşım yoktur. Her şey uygulamanızın amacına ve ölçeğine bağlıdır.

IDistributedCache Interface

NET'teki IDistributedCache arabirimi, dağıtılmış önbellek nesneleriyle etkileşim kurmak için gerekli yöntemleri sağlar. Bu arayüz, belirtilen önbellek üzerinde çeşitli eylemler gerçekleştirmek için aşağıdaki yöntemleri içerir:

GetAsync: Verilen anahtara göre önbellek sunucusundan değeri alır.

SetAsync: Bir anahtar ve değer kabul eder ve değeri belirtilen anahtar altında önbellek sunucusunda saklar.

RefreshAsync: Varsa, önbelleğe alınan öğe için kayan sona erme zamanlayıcısını sıfırlar (kayan sona erme hakkında daha fazla ayrıntı makalenin ilerleyen bölümlerinde ele alınacaktır).

RemoveAsync: Belirtilen anahtara karşılık gelen önbelleğe alınmış verileri siler.

Bu yöntemler, dağıtılmış önbellek üzerinde verimli ve basit işlemler yapılmasını sağlayarak uygulamanızın verilerinin senkronize ve erişilebilir kalmasını sağlar.

Ancak, bu arayüzde eksik görünen bir yöntem CreateOrGetAsync'tir. Bu makalenin ilerleyen bölümlerinde, bunu da eklemek için bir uzantı yazacağız!

Redis nedir?

Redis, veritabanı, önbellek ve mesaj aracısı olarak hareket etmek de dahil olmak üzere birçok amaca hizmet eden açık kaynaklı, bellek içi bir veri deposudur. Dizeler, karmalar, listeler, kümeler ve daha fazlası gibi çeşitli veri yapılarını destekler. Redis, C dilinde yazılmış ve NoSQL veritabanı olarak sınıflandırılmış bir anahtar-değer(key-value) deposu olarak son derece hızlı performansıyla bilinir.

Redis, çok yönlülüğü ve hızı sayesinde Stack Overflow, Flickr, GitHub ve diğerleri gibi teknoloji devleri tarafından yaygın olarak benimsenmiştir. Yüksek verimli işlemlerin üstesinden gelebilme yeteneği, onu önbelleğe alma ve gerçek zamanlı uygulamalar için mükemmel bir seçim haline getirir ve sonuçta kuruluşların uzun vadede maliyetlerden tasarruf etmesine yardımcı olur.

Dağıtık önbellekleme bağlamında Redis, yüksek oranda kullanılabilir bir önbellek uygulamak için ideal bir seçenektir. Veri erişim gecikmesini önemli ölçüde azaltır ve uygulama yanıt süresini iyileştirir, böylece birincil veritabanından önemli miktarda yükü boşaltır. Bu da daha verimli ve duyarlı uygulamaların ortaya çıkmasını sağlar.

Redis'i Docker Konteynerinde Çalıştırın

Bunun için makinenizde Docker / Docker Desktop kurulu olduğundan emin olun.

Redis'i bir Docker Konteynerinde döndürmek için adımlar. Ayrıca Docker'ın genel olarak nasıl çalıştığı ve birkaç önemli docker komutu hakkında çok temel bir fikir edineceksiniz.

1. Docker Hub'dan resmi Redis Görüntüsünü çekin.

Terminalinizi açın ve çalıştırarak en son Redis görüntüsünü çekin:

Terminal penceresi

docker pull redis

2. Redis Konteynerini Çalıştırın

İmaj indirildikten sonra bir Redis Konteyneri başlatın.

Terminal penceresi

docker run --name redis -d -p 6379:6379 redis

docker run : Bu, belirtilen bir Docker imajından yeni bir konteyner oluşturmak ve başlatmak için kullanılan temel komuttur.

--name redis : Bu seçenek konteynere bir isim atar. Bu durumda, konteyner redis olarak adlandırılır. Bir konteyneri adlandırmak, daha sonra referans vermeyi ve yönetmeyi kolaylaştırır.

-d : Bu bayrak “detached mod” anlamına gelir. Bir konteyneri detached modda çalıştırdığınızda, konteyner arka planda çalışır ve terminali kullanmaya devam etmenize izin verir. Bu bayrak olmadan, konteyner ön planda çalışır ve konteynerin günlüklerini doğrudan terminalinizde görürsünüz.

-p 6379:6379 : Konteynerdeki Redis'in varsayılan portu 6379'dur. Komutun bu kısmı, ana bilgisayardaki 6379 numaralı bağlantı noktasını konteynerdeki 6379 numaralı bağlantı noktasıyla eşleştirerek, ana makinenizden 6379 numaralı bağlantı noktasındaki Redis sunucusuna erişmenizi sağlar.

redis : Bu, konteynerin oluşturulduğu Docker görüntüsünün adıdır. Varsayılan olarak, bu komut Docker Hub'dan en son Redis görüntüsünü çeker (local olarak zaten mevcut değilse) ve konteyneri oluşturmak için kullanır.

 

3. Redis'e CLI üzerinden erişme

Şimdi Redis konteynerimiz hazır ve çalışıyor. Konteyner içinde çalışan Redis örneğine erişmek için redis-cli aracını kullanabilirsiniz. İlk olarak, konteyner kabuğuna girin:

Terminal penceresi

docker exec -it redis sh

Ardından, Redis sunucunuzla etkileşim kurmak için Redis CLI'yi kullanın:

Terminal penceresi

redis-cli

4. Temel Redis Komutları

İşte bilmek isteyeceğiniz bazı önemli Redis CLI komutları.

Önbellek Veri Girişi Ayarlama

Terminal penceresi

-> Set name “Mustafa”

OK.

Anahtara göre önbellek girdisini alma

Terminal penceresi

-> Get name

“Mustafa”

Önbellek Girdisini Silme

Terminal penceresi

-> Get name

“Mustafa”

-> Del name

(integer)1

-> Get name

(nil)

 

5. Redis Insight (Önerilen)

Redis'i bir anahtar-değer deposu veya önbellek olarak kullanmanın yanı sıra, Redis örneklerinizi izlemek ve yönetmek için Redis Insight'tan yararlanmanızı öneririm.

Redis Insight, Redis verilerinizin ve performans ölçümlerinizin gerçek zamanlı olarak ayrıntılı bir görünümünü sağlayan güçlü bir grafik kullanıcı arayüzü (GUI) aracıdır. Redis Insight ile bellek kullanımı, saniye başına komutlar ve istemci bağlantıları gibi önemli ölçümleri kolayca izleyebilir ve Redis dağıtımınızı daha iyi performans için optimize edebilirsiniz.

Ek olarak Redis Insight, Redis veritabanlarınızla etkileşim kurmak için uygun bir yol sunarak anahtarları görüntülemenizi ve yönetmenizi, komutları çalıştırmanızı ve sorunları verimli bir şekilde gidermenizi sağlar.

Genel olarak Redis Insight, Redis örnekleri hakkında daha derin bilgiler edinmek ve Redis dağıtımlarını maksimum verimlilik için optimize etmek isteyen geliştiriciler ve yöneticiler için değerli bir araçtır.

İndirmek için buraya tıklayın.

 

ASP.NET Core'da Redis ile Önbelleğe Alma

ASP.NET Core'da Redis ile Distributed Caching'i uygulamaya başlayalım.

Bu API, Entity Framework Core aracılığıyla bir PostgreSQL Veritabanına bağlı ve Ürün Modeli üzerinde bazı CRUD İşlemlerine sahip. Ayrıca, veritabanı eklediğimiz yaklaşık 1000+ ürün kaydına sahip. Referansınız için SQL Script dosyasını da bu depoya ekledim. PostgreSQL veritabanınıza 1000 ürün verisi eklemek için bunu kullanmaktan çekinmeyin.

İlk olarak, Redis sunucusu ile iletişim kurmanıza yardımcı olacak bir paket yükleyelim.

Install-Package Microsoft.Extensions.Caching.StackExchangeRedis

Redis Container'ınızın çalışır durumda olduğundan emin olun.

Bundan sonra, .NET uygulamamızı Redis önbelleğini destekleyecek şekilde yapılandırmamız gerekiyor. Bunu yapmak için, Program.cs dosyasına gidin ve aşağıdakileri ekleyin.

builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = "localhost";
    options.ConfigurationOptions = new StackExchange.Redis.ConfigurationOptions
    {
        AbortOnConnectFail = true,
        EndPoints = { options.Configuration }
    };
});

Yukarıdaki kod, AddStackExchangeRedisCache yöntemini kullanarak bir ASP.NET Core uygulamasında Redis önbelleğini yapılandırır. Redis sunucusunun bağlantı dizesini “localhost” olarak ayarlar, bu da Redis sunucusunun yerel makinede çalıştığını gösterir.

AbortOnConnectFail seçeneği true olarak ayarlanır, yani Redis'e bağlanamazsa uygulama hemen başarısız olur. EndPoints özelliği, options.Configuration'da belirtilen aynı bağlantı dizesini kullanacak şekilde yapılandırılır ve Redis sunucusu için uç noktanın doğru şekilde ayarlanması sağlanır.

IDistributedCache.SetAsync yöntemi 3 parametre geçmemizi gerektirir.

  1. Önbellek Anahtarı
  2. Veri, ancak bayt dizisi olarak.
  3. IMemoryCache'e benzer DistributedCacheEntryOptions

Her nesneyi Serialize etmeye başlamak ve ondan bir bayt dizisi oluşturmak kötü bir fikir olabilir. Bununla mücadele etmek için, önbellek işlemlerini daha da sorunsuz hale getiren bazı uzantı yöntemleri ekleyelim.

public static class DistributedCacheExtensions
{
    private static readonly JsonSerializerOptions SerializerOptions = new()
    {
        PropertyNamingPolicy = null,
        WriteIndented = true,
        AllowTrailingCommas = true,
        DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
    };

    public static Task SetAsync<T>(this IDistributedCache cache, string key, T value)
    {
        return SetAsync(cache, key, value, new DistributedCacheEntryOptions()
            .SetSlidingExpiration(TimeSpan.FromMinutes(30))
            .SetAbsoluteExpiration(TimeSpan.FromHours(1)));
    }

    public static Task SetAsync<T>(this IDistributedCache cache, string key, T value, DistributedCacheEntryOptions options)
    {
        var bytes = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(value, SerializerOptions));
        return cache.SetAsync(key, bytes, options);
    }

    public static bool TryGetValue<T>(this IDistributedCache cache, string key, out T? value)
    {
        var val = cache.Get(key);
        value = default;
        if (val == null) return false;
        value = JsonSerializer.Deserialize<T>(val, SerializerOptions);

        return true;
    }

    public static async Task<T?> GetOrSetAsync<T>(this IDistributedCache cache, string key, Func<Task<T>> task, DistributedCacheEntryOptions? options = null)
    {
        options ??= new DistributedCacheEntryOptions()
            .SetSlidingExpiration(TimeSpan.FromMinutes(30))
            .SetAbsoluteExpiration(TimeSpan.FromHours(1));
        if (cache.TryGetValue(key, out T? value) && value is not null)
        {
            return value;
        }

        value = await task();
        if (value is not null)
        {
            await cache.SetAsync<T>(key, value, options);
       }

        return value;
    }
}

 

Burada, SetAsync ve TryGetValue olmak üzere 2 Çekirdek Yöntem vardır. Kalan 2 yöntem bu çekirdek yöntemlerin etrafını sarar. Her neyse, şimdi bunların üzerinden geçelim. Tüm bu eklenti yöntemlerinin IDistributedCache arayüzünün üzerinde olduğunu unutmayın.

SetAsync

Adından da anlaşılacağı gibi, bu, T türünde gelen değeri serileştirmenize ve ardından bayt dizisini oluşturmanıza yardımcı olur. Bu veri, önbellek anahtarıyla birlikte orijinal SetAsync yöntemine aktarılır. Verileri serileştirirken JsonSerializerOptions örneğini de geçiriyoruz.

Varsayılan olarak, herhangi bir önbellek anahtarının mutlak sona erme süresi 1 saat ve kayan sona erme süresi 30 dakikadır.

TryGetValue

Bu yöntem, iletilen önbellek anahtarına göre önbellekten veri getirir. Eğer bulunursa, veriyi T tipinde deserialize eder ve değeri ayarlar ve true değerini geri döndürür. Anahtar Redis önbellek belleğinde bulunamazsa, false döndürür.

GetOrSetAsync

Bu, yukarıdaki uzantıların her ikisinin de etrafını saran bir sarmalayıcıdır. Temel olarak, bu tek yöntemde, hem Get hem de Set işlemlerini kusursuz bir şekilde gerçekleştirir. Eğer Cache Key Redis'te bulunursa, veriyi döndürür. Bulunamazsa, iletilen görevi (lambda işlevi) çalıştırır ve döndürülen değeri önbellek belleğine ayarlar.

Product Service

İşte bir önceki adımda tasarladığımız uzantı yöntemlerini tüketecek olan ProductService sınıfı.

public class ProductService(AppDbContext context, ILogger<ProductService> logger, IDistributedCache cache): IProductService
{
    public async Task AddSync(ProductCreationDto request)
    {
        var product = new Product { Name = request.Name, Description = request.Description, Price = request.Price };
        await context.Products.AddAsync(product);
        await context.SaveChangesAsync();

        // invalidate cache for products, as new product is added
        var cacheKey = "products";
        logger.LogInformation("invalidating cache for key: {CacheKey} from cache", cacheKey);
        await cache.RemoveAsync(cacheKey);
    }

    public async Task<Product> GetAsync(int id)
    {
        var cacheKey = $"product:{id}";
        logger.LogInformation("fetching data for key: {CacheKey} from cache", cacheKey);
        var product = await cache.GetOrSetAsync(cacheKey,
            async () =>
            {
                logger.LogInformation("cache miss. fetching data for key: {CacheKey} from database", cacheKey);
                return await context.Products.FindAsync(id)!;
            })!;
        return product!;
    }

    public async Task<List<Product>> GetAllAsync()
    {
        var cacheKey = "products";
        logger.LogInformation("fetching data for key: {CacheKey} from cache", cacheKey);
        var cacheOptions = new DistributedCacheEntryOptions()
            .SetAbsoluteExpiration(TimeSpan.FromMinutes(20))
            .SetSlidingExpiration(TimeSpan.FromMinutes(2));
        var products = await cache.GetOrSetAsync(
            cacheKey,
            async () =>
            {
                logger.LogInformation("cache miss. fetching data for key: {CacheKey} from database", cacheKey);
                return await context.Products.ToListAsync();
            },
            cacheOptions)!;

        return products!;
    }
}

IDistributedCache ve AppDbContext örneklerinin bu sınıfın birincil yapıcısına enjekte edildiğine dikkat edin.

Get Metodunda, daha önce olduğu gibi önbellek anahtarını oluşturuyoruz ve GetOrSetAsync uzantısını kullanıyoruz. Buna önbellek anahtarını aktarıyoruz ve değer önbellekte bulunmazsa (bir önbellek kaçırma), değeri veritabanından almak için sağlanan temsilciyi (eşzamansız bir lambda işlevi) çalıştırıyor. Ardından ürün verileri döndürülür.

GetAll yöntemi de SlidingExpiration ve AbsoluteExpiration için DistributedCacheEntryOptions özelliklerini sırasıyla 2 ve 20 dakika olarak açıkça ayarlamamız dışında benzerdir.

Uygulamayı oluşturalım ve çalıştıralım. Uç noktaları test etmek için Postman kullandım.

Bana doğrudan veritabanından 1000'den fazla ürünün listesini döndürecek olan /products GET uç noktasını çağırdım. Ve işte yanıtın ne kadar zaman aldığı.

Bundan sonra, bu ürün listesinin Redis önbelleğinde saklanmasını bekliyoruz. Uç noktaya bir kez daha gidelim ve bu sefer verinin önbellekten alınmasını bekliyoruz. İşte sonraki çağrılardaki yanıt süresi.

32ms

Ayrıca /products/:id uç noktasına da ulaşmaya çalıştım.

Daha önce önerildiği gibi, Redis örneğinizi analiz etmek ve izlemek için bu harika Redis Visualizer aracını da kullanabilirsiniz. İşte göz atabileceğimiz önbelleğe alınmış veriler.

Bu makale bu kadar. Performansı artırmak için ASP.NET Core uygulamalarınızı nasıl önbelleğe alırsınız?

Özet

Bu detaylı makalede, Distributed Caching, Redis, Docker Container üzerinde Redis kurulumu, IDistributedCache arayüzü, Redis Insight istemcisi ve son olarak ASP.NET Core'da Distributed Caching'i Redis ile entegre etmek için bir örnek hakkında bilgi edindik. Umarım bu yazıda yeni ve detaylı bir şeyler öğrenmişsinizdir.

Herhangi bir yorumunuz veya öneriniz varsa, lütfen bunları aşağıdaki yorum bölümünde bırakın. Bu makaleyi geliştirici topluluğunuzda paylaşmayı unutmayın. Teşekkürler ve Mutlu Kodlamalar!

Source Code : Distributed Caching Redis ASP.NET CORE Web API