NET Core'da Cache Yönetimi (In-Memory) - 1
Bu yazı seri halinde yazılacaktır. Cache nedir ? Neden ihtiyaç duyarız. Cache kullanmanın avantajları, dezavantajları monolith yada distributed sistemler de Cache Yönetimi nasıl olmalıdır? Cache Yönetiminde kullanılan veri depolama çözümleri nelerdir? sorularına cevap vermeye örneklerle anlatmaya çalışacağım.
Cache Nedir?
Cache, sık kullanılan veya bir veri kaynağından sıkça talep edilen verilerin geçici olarak kaynağa gitmeden tüketmek için tutulduğu depolama alanıdır. İstenilen veriye ihtiyaç duyulduğunda bu veriyi kaynağa gitmeden hızlıca tüketmek için kullanırız. Verilerin çok sık değişmediği durumlara örnek olarak e-ticaret sitelerindeki kategori ve alt kategoriler ya da kullanıcı bilgilerini verebiliriz. Bir e-ticaret sitesine binlerce kişinin girdiğini ve her kullanıcı için bu verileri veritabanından tüketmek istediğimizde performans anlamında ciddi bir yavaşlama olacaktır. Bu tarz senaryolarda verileri kaynaktan değil de önbellekten almak çok daha hızlıdır. Sunucuya düşen yük azalır, uygulamamızın daha fazla kullanıcıya verdiği hizmetin kalitesi artar.
In-Memory (Bellek İçi) Cache
Veriler uygulamanın çalıştığı sunucunun RAM'inde saklanır. Bilgiler uygulama çalışırken RAM'de tutulduğundan dolayı uygulama kapandığı anda RAM'de ki verilerde kaybolur.Basit ve tek sunuculu mimariler için idealdir.
- Web Farm ve Sticky Session Kavramları
Bir uygulama birden çok sunucunun bir araya gelerek tek bir sistem gibi çalıştığı bir sunucu grubu ortamında barındırılıyor olabilir. Bu sunuculara gelen istekler load balancer ile sunuculara dağıtılır. Burada Sticky Session kavramı devreye girer.
Atılan bir istek Sunucu A'ya gittiğinde load balancer bu kullanıcıyı not eder ve sonraki isteklerinde hep Sunucu A'ya yönlendirir.
In-Memory Cache kullanıldığında her sunucu kendine ait bir hafızası ve dolayısıyla kendine ait bir önbelleği vardır. Eğer Sticky Session olmazsa bi kullanıcı isteği farklı sunuculara gidebilir ve bu da daha önce başka bir sunucuda oluşturulmuş önbellek verisine erişememesine yol açar. Her bir sunucu kendi hafızasındaki veriyi barındırdığı için bir sunucuda güncellenen veri diğerlerinde eski kalır. Bu durum kullanıcıya farklı sunucular üzerinden servis edildiğinde tutarsız sonuçların görülmesine sebebiyet verir. Biz bu sorunu Cache Inconsistecy olarak adlandırıyoruz.Sticky Sessions ile tüm istekler aynı sunucuya gitse bile sunucunun çökme durumunda veya yeniden başlatılması durumunda veri kaybına sebep olur.
Dolayısıyla in-memory cache kullanma senaryolarını iyi analiz edip yapıyı buna göre kurgulamak gerekiyor.
In-Memory Cache Kullanımı
// IMemoryCache servisini ekleyin builder.Services.AddMemoryCache();
// Product modeli ekleyin
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
namespace WebApiInMemory.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class ProductsController : ControllerBase
{
private readonly IMemoryCache _memoryCache;
private const string ProductsCacheKey = "ProductList";
public ProductsController(IMemoryCache memoryCache)
{
_memoryCache = memoryCache;
}
[HttpGet]
public IActionResult GetProducts()
{
// Cache'ten veriyi TryGetValue ile almaya çalışır
if (!_memoryCache.TryGetValue(ProductsCacheKey, out List<Product> products))
{
// Cache'te yoksa, örnek veri oluştur (gerçek uygulamada veritabanından gelir)
products = GetSampleProducts();
// Cache ayarları: 1 dakika sliding expiration, 5 dakika absolute expiration
var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromMinutes(1))
.SetAbsoluteExpiration(TimeSpan.FromMinutes(5))
.SetPriority(CacheItemPriority.Normal)
.RegisterPostEvictionCallback((key, value, reason, state) =>
{
// Cache'in nasıl davrandığını izlemek için hangi verilerin ne zaman ne neden silindiğini görmek istersek callback'i ekleyebiliriz
Console.WriteLine($"Cache öğesi silindi: {key}, Sebep: {reason}");
});
// Cache'e ekle
_memoryCache.Set(ProductsCacheKey, products, cacheEntryOptions);
}
return Ok(products);
}
[HttpGet("{id}")]
public IActionResult GetProduct(int id)
{
// Önce tüm ürün listesini cache'ten al
if (_memoryCache.TryGetValue(ProductsCacheKey, out List<Product> products))
{
// Gelen listeden istenen idli ürünü bul
var product = products.FirstOrDefault(p => p.Id == id);
if (product == null)
return NotFound();
return Ok(product);
}
return NotFound("Cache bulunamadı veya ürün listesi boş.");
}
[HttpPost]
public IActionResult CreateProduct([FromBody] Product newProduct)
{
// Yeni ürünü eklemek için cache'teki listeyi güncelle
if (_memoryCache.TryGetValue(ProductsCacheKey, out List<Product> products))
{
// Yeni ID ata (basitçe max + 1)
newProduct.Id = products.Max(p => p.Id) + 1;
products.Add(newProduct);
// Cache'i güncelle (mevcut cache ayarlarını korumak için yeniden Set yapabiliriz)
_memoryCache.Set(ProductsCacheKey, products);
return CreatedAtAction(nameof(GetProduct), new { id = newProduct.Id }, newProduct);
}
else
{
// Eğer cache yoksa, yeni bir liste oluştur
var newList = new List<Product> { newProduct };
newProduct.Id = 1;
_memoryCache.Set(ProductsCacheKey, newList);
return CreatedAtAction(nameof(GetProduct), new { id = newProduct.Id }, newProduct);
}
}
[HttpPut("{id}")]
public IActionResult UpdateProduct(int id, [FromBody] Product updatedProduct)
{
if (!_memoryCache.TryGetValue(ProductsCacheKey, out List<Product> products))
return NotFound("Cache bulunamadı.");
var existingProduct = products.FirstOrDefault(p => p.Id == id);
if (existingProduct == null)
return NotFound();
// Ürünü güncelle
existingProduct.Name = updatedProduct.Name;
existingProduct.Price = updatedProduct.Price;
// Cache'i güncelle
_memoryCache.Set(ProductsCacheKey, products);
return NoContent();
}
[HttpDelete("{id}")]
public IActionResult DeleteProduct(int id)
{
if (!_memoryCache.TryGetValue(ProductsCacheKey, out List<Product> products))
return NotFound("Cache bulunamadı.");
var productToRemove = products.FirstOrDefault(p => p.Id == id);
if (productToRemove == null)
return NotFound();
products.Remove(productToRemove);
_memoryCache.Set(ProductsCacheKey, products);
return NoContent();
}
[HttpDelete("clear")]
public IActionResult ClearCache()
{
_memoryCache.Remove(ProductsCacheKey);
return Ok("Ürün cache'i temizlendi.");
}
}
}
Bir sonraki konuda cache yönetimi sorunlarına devam ediyoruz. Okumak için aşağıdaki linkten devam edebilirsiniz.