UZMAN
Health Check & Resilience
Redis bağlantı durumu izleme, otomatik kurtarma ve graceful degradation.
Kod örneği görünümü
Bu sayfadaki eşleşen örnekleri seçilen istemciye göre gösterir.
Ne Zaman Health Check / Resilience Pattern Kullan
| Kullan | Kullanma | Gerçek Hayat |
|---|---|---|
| Redis cache'in düştüğünü hızlı anlamak gerekiyor | Tek test ortamı, Redis local | SaaS: K8s readiness probe Redis ping — pod traffic'ten çıkar |
| Circuit breaker ile cascade failure engellemek | Basit script, hata tolere edilebilir | Fintech: Redis timeout → breaker açılır → DB fallback (yavaş ama çalışır) |
| Graceful degradation (cache miss → DB) | Cache olmadan çalışamayan mimari (tasarım hatası) | E-ticaret: Black Friday'de Redis OOM → cache bypass, DB'ye düş, satış durmaz |
| Multi-service ortam, her servisin health'i izleniyor | Monolith + tek Redis, ops team yok | Microservices: Tüm pod'lar /health/ready raporlar → LB sağlıksız pod'u çıkarır |
Gerçek hayat senaryosu — Graceful degradation: E-ticaret ürün kataloğu Redis'ten okunuyor. Redis down → circuit breaker OPEN → cache bypass aktif → DB'den oku (50ms vs 0.3ms ama çalışır). Kullanıcı fark etmez, sadece P99 yükselir. Monitoring alert → ops team müdahale eder.
PING # PONG
INFO server # versiyon, uptime
INFO memory # used_memory, fragmentation
INFO clients # connected_clients
INFO stats # total_commands_processed
// Health Check
builder.Services.AddHealthChecks()
.AddRedis(
builder.Configuration.GetConnectionString("Redis")!,
name: "redis",
tags: new[] { "ready", "infrastructure" });
// 📌 NuGet: AspNetCore.HealthChecks.Redis (community) veya
// Microsoft.Extensions.Diagnostics.HealthChecks (built-in .NET 8+)
// Built-in: sadece PING. Custom check ile daha detaylı kontrol:
// - Memory fragmentation ratio
// - Connected clients vs maxclients
// - Son RDB save zamanı
// - Replication lag
// Polly Resilience Pipeline
builder.Services.AddSingleton(sp =>
{
return new ResiliencePipelineBuilder()
.AddRetry(new RetryStrategyOptions
{
MaxRetryAttempts = 3,
Delay = TimeSpan.FromMilliseconds(100),
BackoffType = DelayBackoffType.Exponential,
ShouldHandle = new PredicateBuilder().Handle<RedisConnectionException>()
})
.AddCircuitBreaker(new CircuitBreakerStrategyOptions
{
FailureRatio = 0.5,
MinimumThroughput = 10,
BreakDuration = TimeSpan.FromSeconds(15),
ShouldHandle = new PredicateBuilder().Handle<RedisConnectionException>()
})
.AddTimeout(TimeSpan.FromSeconds(2))
.Build();
});
// Graceful degradation wrapper
public class ResilientCacheService
{
private readonly IDatabase _redis;
private readonly ResiliencePipeline _pipeline;
private readonly ILogger<ResilientCacheService> _logger;
public ResilientCacheService(
IConnectionMultiplexer mux,
ResiliencePipeline pipeline,
ILogger<ResilientCacheService> logger)
{
_redis = mux.GetDatabase();
_pipeline = pipeline;
_logger = logger;
}
public async Task<T?> GetAsync<T>(string key) where T : class
{
try
{
var result = await _pipeline.ExecuteAsync(async ct =>
{
var value = await _redis.StringGetAsync(key);
return value.HasValue ? JsonSerializer.Deserialize<T>(value!) : null;
});
return result;
}
catch (Exception ex)
{
// Redis down → graceful degradation (null döner, DB'den okunur)
_logger.LogWarning(ex, "Redis unavailable for key {Key}", key);
return null;
}
}
}
Cache asla single point of failure olmamalı. Redis down → uygulama yavaşlar ama çalışmaya devam eder.
Custom Health Check (Detaylı)
Built-in health check sadece PING yapar. Production'da memory, fragmentation, client count ve persistence durumunu kontrol eden custom check gerekir.
public class RedisDetailedHealthCheck : IHealthCheck
{
private readonly IConnectionMultiplexer _mux;
public RedisDetailedHealthCheck(IConnectionMultiplexer mux) => _mux = mux;
public async Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context, CancellationToken ct = default)
{
try
{
var server = _mux.GetServers().First();
var info = await server.InfoAsync();
var memory = info.First(s => s.Key == "memory")
.ToDictionary(p => p.Key, p => p.Value);
var clients = info.First(s => s.Key == "clients")
.ToDictionary(p => p.Key, p => p.Value);
var persistence = info.First(s => s.Key == "persistence")
.ToDictionary(p => p.Key, p => p.Value);
var usedMemory = long.Parse(memory["used_memory"]);
var maxMemory = long.Parse(memory.GetValueOrDefault("maxmemory") ?? "0");
var fragRatio = double.Parse(memory["mem_fragmentation_ratio"]);
var connectedClients = int.Parse(clients["connected_clients"]);
var lastSave = long.Parse(persistence["rdb_last_save_time"]);
var data = new Dictionary<string, object>
{
["used_memory_mb"] = usedMemory / 1024 / 1024,
["fragmentation_ratio"] = fragRatio,
["connected_clients"] = connectedClients,
["last_save_minutes_ago"] = (DateTimeOffset.UtcNow.ToUnixTimeSeconds() - lastSave) / 60
};
// Degraded: memory >70% veya fragmentation >1.5
if (maxMemory > 0 && (double)usedMemory / maxMemory > 0.8)
return HealthCheckResult.Unhealthy("Memory >80%", data: data);
if (fragRatio > 1.5)
return HealthCheckResult.Degraded("High fragmentation", data: data);
if ((DateTimeOffset.UtcNow.ToUnixTimeSeconds() - lastSave) > 7200)
return HealthCheckResult.Degraded("No RDB save in 2h", data: data);
return HealthCheckResult.Healthy("OK", data);
}
catch (Exception ex)
{
return HealthCheckResult.Unhealthy("Redis unreachable", ex);
}
}
}
// Kayıt
builder.Services.AddHealthChecks()
.AddCheck<RedisDetailedHealthCheck>("redis-detailed",
tags: new[] { "ready", "infrastructure" });