RRedis Handbook

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.
CLOSED Normal — istekler geçer OPEN Kırık — istekler reddedilir HALF-OPEN Test — 1 istek dene N hata → aç timeout sonrası başarılı → kapat başarısız → tekrar aç

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" });