DevOps & Cloud
Redis ile Cache Yönetimi: .NET Core ve Performans Optimizasyonu
Redis, yuksek performansli bir in-memory veri yapisi deposu olup distributed cache, session yonetimi, mesaj kuyrugu ve real-time uygulamalar icin vazgecilmez bir aractir. .NET ekosisteminde IDistributedCache arayuzu ile sorunsuz entegrasyon saglar ve SignalR backplane olarak coklu sunucu ortamlarinda real-time iletisimi mumkun kilar. Bu yazida Redis cache stratejilerini, .NET entegrasyonunu ve production ortamlarindaki best practice'leri detayli olarak inceleyecegim.
Projelerimde Redis kullanarak API response surelerini %80'e kadar dusurdugumuz ve veritabani yukunu onemli olcude azalttigimizi deneyimledim. Dogru cache stratejisi, uygulamanizin performansini dramatik sekilde artirir.
Redis .NET Entegrasyonu
IDistributedCache ile Temel Kullanim
.NET'in IDistributedCache arayuzu, cache provider'dan bagimsiz bir soyutlama saglar. Redis'e gecis yapmak icin sadece kayit degisikligi yeterlidir.
// Program.cs - Redis cache kayit
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = builder.Configuration.GetConnectionString("Redis");
options.InstanceName = "MyApp:";
});
// Servis katmaninda cache kullanimi
public class ProductService
{
private readonly IDistributedCache _cache;
private readonly ApplicationDbContext _context;
private static readonly DistributedCacheEntryOptions CacheOptions = new()
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30),
SlidingExpiration = TimeSpan.FromMinutes(10)
};
public ProductService(IDistributedCache cache, ApplicationDbContext context)
{
_cache = cache;
_context = context;
}
public async Task<ProductDto?> GetProductAsync(int id, CancellationToken ct)
{
var cacheKey = $"product:{id}";
// Cache'den okuma
var cached = await _cache.GetStringAsync(cacheKey, ct);
if (cached is not null)
return JsonSerializer.Deserialize<ProductDto>(cached);
// Veritabanindan cek ve cache'e yaz
var product = await _context.Products
.Where(p => p.Id == id)
.Select(p => new ProductDto(p.Id, p.Name, p.Price))
.FirstOrDefaultAsync(ct);
if (product is not null)
{
var json = JsonSerializer.Serialize(product);
await _cache.SetStringAsync(cacheKey, json, CacheOptions, ct);
}
return product;
}
public async Task InvalidateProductCacheAsync(int id, CancellationToken ct)
{
await _cache.RemoveAsync($"product:{id}", ct);
}
}StackExchange.Redis ile Ileri Seviye Islemler
IDistributedCache basit senaryolar icin yeterli olsa da, Redis'in tum gucunden yararlanmak icin StackExchange.Redis kutuphanesini dogrudan kullanabilirsiniz:
// Redis connection multiplexer kayit
builder.Services.AddSingleton<IConnectionMultiplexer>(sp =>
{
var config = ConfigurationOptions.Parse(
builder.Configuration.GetConnectionString("Redis")!);
config.AbortOnConnectFail = false;
config.ConnectRetry = 3;
config.ConnectTimeout = 5000;
config.SyncTimeout = 3000;
return ConnectionMultiplexer.Connect(config);
});
// Ileri seviye Redis operasyonlari
public class RedisCacheService
{
private readonly IDatabase _db;
public RedisCacheService(IConnectionMultiplexer redis)
{
_db = redis.GetDatabase();
}
// Sorted Set ile leaderboard
public async Task UpdateLeaderboardAsync(string userId, double score)
{
await _db.SortedSetAddAsync("leaderboard", userId, score);
}
public async Task<List<LeaderboardEntry>> GetTopPlayersAsync(int count)
{
var entries = await _db.SortedSetRangeByRankWithScoresAsync(
"leaderboard", 0, count - 1, Order.Descending);
return entries.Select(e => new LeaderboardEntry(
e.Element.ToString(), e.Score)).ToList();
}
// Hash ile kullanici oturumu
public async Task SetUserSessionAsync(string sessionId, UserSession session)
{
var hashEntries = new HashEntry[]
{
new("userId", session.UserId),
new("email", session.Email),
new("role", session.Role),
new("loginAt", session.LoginAt.ToString("O"))
};
await _db.HashSetAsync($"session:{sessionId}", hashEntries);
await _db.KeyExpireAsync($"session:{sessionId}", TimeSpan.FromHours(24));
}
// Distributed lock
public async Task<bool> AcquireLockAsync(string resource, TimeSpan expiry)
{
return await _db.StringSetAsync(
$"lock:{resource}", Environment.MachineName, expiry, When.NotExists);
}
}SignalR Redis Backplane
Coklu Sunucu Ortaminda Real-Time Iletisim
Uygulamaniz birden fazla sunucuda calistiginda, SignalR mesajlarinin tum sunuculara iletilmesi gerekir. Redis backplane bu sorunu cozer.
// Program.cs - SignalR Redis backplane yapilandirmasi
builder.Services.AddSignalR()
.AddStackExchangeRedis(builder.Configuration.GetConnectionString("Redis")!, options =>
{
options.Configuration.ChannelPrefix = RedisChannel.Literal("MyApp");
});
// SignalR Hub
public class NotificationHub : Hub
{
public async Task SendToGroup(string groupName, string message)
{
// Bu mesaj Redis uzerinden tum sunuculara iletilir
await Clients.Group(groupName).SendAsync("ReceiveNotification", message);
}
public async Task JoinGroup(string groupName)
{
await Groups.AddToGroupAsync(Context.ConnectionId, groupName);
}
}Cache Stratejileri
Cache-Aside (Lazy Loading)
En yaygin stratejidir. Veri oncelikle cache'den okunur, bulunamazsa kaynaktan cekilir ve cache'e yazilir. Yukaridaki ProductService ornegi bu pattern'i kullanir.
Write-Through
Veri yazma islemlerinde hem veritabani hem de cache esanli olarak guncellenir. Veri tutarliligi kritik olan senaryolarda tercih edilir.
Cache Invalidation Stratejileri
Cache invalidation, distributed sistemlerde en zorlu problemlerden biridir. Yanlis invalidation, stale data veya gereksiz cache miss'lere yol acar:
- TTL (Time to Live): Otomatik suresi dolma ile basit invalidation
- Event-based: Veri degistiginde aktif olarak cache'i temizleme
- Tag-based: Iliskili cache kayitlarini gruplandirarak toplu temizleme
- Pattern-based: Key pattern'ine gore toplu silme
// Pattern-based cache invalidation ornegi
public class CacheInvalidationService
{
private readonly IConnectionMultiplexer _redis;
public CacheInvalidationService(IConnectionMultiplexer redis)
{
_redis = redis;
}
// Belirli bir kategorideki tum urunlerin cache'ini temizle
public async Task InvalidateCategoryProductsAsync(int categoryId)
{
var server = _redis.GetServer(_redis.GetEndPoints().First());
var db = _redis.GetDatabase();
var keys = server.Keys(pattern: $"MyApp:product:category:{categoryId}:*");
foreach (var key in keys)
{
await db.KeyDeleteAsync(key);
}
}
// Write-through pattern ile cache ve DB'yi esanli guncelle
public async Task UpdateProductWithCacheAsync(
int id, ProductUpdateDto dto, ApplicationDbContext context)
{
var db = _redis.GetDatabase();
// Veritabanini guncelle
var product = await context.Products.FindAsync(id);
if (product is null) return;
product.Name = dto.Name;
product.Price = dto.Price;
await context.SaveChangesAsync();
// Cache'i guncelle (write-through)
var cacheKey = $"MyApp:product:{id}";
var json = JsonSerializer.Serialize(new ProductDto(id, dto.Name, dto.Price));
await db.StringSetAsync(cacheKey, json, TimeSpan.FromMinutes(30));
}
}Redis Streams ile Event-Driven Mimari
Redis Streams, Kafka benzeri bir mesajlasma yapisini Redis uzerinde kurmanizi saglar. Consumer group'lar ile birden fazla tuketici arasinda mesaj dagitimi yapilabilir:
// Redis Streams ile mesaj gonderme
public async Task PublishOrderEventAsync(OrderEvent orderEvent)
{
var db = _redis.GetDatabase();
var entries = new NameValueEntry[]
{
new("type", orderEvent.Type),
new("orderId", orderEvent.OrderId.ToString()),
new("data", JsonSerializer.Serialize(orderEvent)),
new("timestamp", DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString())
};
await db.StreamAddAsync("order-events", entries, maxLength: 10000);
}
// Consumer group ile mesaj okuma
public async Task ConsumeOrderEventsAsync(CancellationToken ct)
{
var db = _redis.GetDatabase();
var groupName = "order-processor";
var consumerName = Environment.MachineName;
// Consumer group olustur (yoksa)
try { await db.StreamCreateConsumerGroupAsync("order-events", groupName, "0"); }
catch (RedisServerException) { /* Grup zaten var */ }
while (!ct.IsCancellationRequested)
{
var entries = await db.StreamReadGroupAsync(
"order-events", groupName, consumerName, ">", count: 10);
foreach (var entry in entries)
{
// Mesaji isle
var data = entry.Values.First(v => v.Name == "data").Value;
await ProcessOrderEventAsync(data.ToString());
// Mesaji onayla (acknowledge)
await db.StreamAcknowledgeAsync("order-events", groupName, entry.Id);
}
await Task.Delay(100, ct);
}
}Redis Izleme ve Performans Analizi
redis-cli ile Monitoring
Production ortaminda Redis performansini izlemek icin redis-cli guclu bir aractir:
# Canli komut izleme (dikkat: production'da performans etkisi olabilir)
redis-cli MONITOR
# Redis sunucu istatistikleri
redis-cli INFO stats
# Bellek kullanim detaylari
redis-cli INFO memory
# Bagli istemcileri listele
redis-cli CLIENT LIST
# Yavas sorgulari logla (10ms'den yavas)
redis-cli CONFIG SET slowlog-log-slower-than 10000
redis-cli SLOWLOG GET 10
# Key sayisi ve boyut analizi
redis-cli DBSIZE
redis-cli INFO keyspace
# Belirli bir key pattern'inin bellek kullanimini analiz et
redis-cli --bigkeysBellek Optimizasyonu
Redis bellek kullanimimini optimize etmek icin su stratejileri uygulayabilirsiniz:
- maxmemory-policy:
allkeys-lruen yaygin tercih olup, bellek dolunca en az erisilen key'leri siler - Key expiration: Her key icin uygun TTL belirleyin, suresi belirsiz key'lerden kacinin
- Veri sıkistirma: Buyuk JSON degerlerini LZ4 veya Gzip ile sikistirarak depolama alanini azaltin
- Hash yerine String: Az alanli kucuk nesneler icin hash veri yapisi bellek acisindan daha verimlidir
- Key isimlendirme: Kisa ama anlamli key isimleri kullanarak bellek israfini onleyin
Redis Yapilandirma Onerileri
Production ortami icin onemli ayarlar:
- maxmemory-policy:
allkeys-lruveyavolatile-lruile bellek yonetimi - appendonly: Veri kaliciligi icin AOF (Append Only File) etkinlestirme
- Sentinel/Cluster: Yuksek erisilebilirlik icin Redis Sentinel veya Cluster modu
- Connection pooling: ConnectionMultiplexer'i singleton olarak kullanin, her istekte yeni baglanti acmayin
Pratik Oneriler ve Ozet
Redis cache yonetiminde su noktalara dikkat edin:
- Cache key tasarimi: Tutarli ve anlamli key isimlendirmesi kullanin (orn.
entity:id:field) - TTL stratejisi: Her veri tipi icin uygun suresi dolma suresi belirleyin
- Monitoring: Redis INFO komutu ve RedisInsight ile performansi izleyin
- Bellek yonetimi: maxmemory ayarini sunucu RAM'inin %70'inden fazla yapmayin
- Serialization: System.Text.Json ile hizli ve verimli serialization kullanin
Redis, dogru kullanildiginda uygulamanizin performansini katbekat artirir. Onemli olan, her senaryo icin dogru veri yapisini ve cache stratejisini secmektir.
İlgili Makaleler
.NET'te Caching Stratejileri: In-Memory, Distributed ve Redis
.NET'te etkili caching stratejileri uygulayın. In-memory cache, distributed cache ve Redis entegrasyonu.
.NET Performance Optimizasyonu: Profiling ve Best Practices
.NET uygulamalarının performansını optimize edin. Profiling araçları, memory management ve async patterns.
SignalR ile Gerçek Zamanlı .NET Uygulamaları
SignalR ile gerçek zamanlı web uygulamaları geliştirin. Hub yapısı, grup yönetimi, Redis backplane ve ölçekleme stratejileri.
Flutter Projeniz mi Var?
iOS, Android ve web için yüksek performanslı Flutter uygulamaları geliştiriyorum.
İletişime Geç