O dia em que o Redis caiu e o sistema inteiro parou | Tech na Prática
- O dia em que o Redis caiu e o sistema inteiro parou | Tech na Prática
Circuit-Breaker no Redis
- Foi implementado
circuit-breakerno Redis porque ele estava apresentando falhas pelo fato de ter atingido o limite de memória. Importante implementar esse mecanismo para termos resiliência. Isso é importante para evitar a queda total da API. - Foi aplicado modo cluster no Redis para termos alta disponibilidade e também com isso foi aplicado failover automático quando um nó está inativo
API
└── Circuit Breaker
├── Redis
└── Fallback (MemoryCache / default)
Exemplo em .NET
- Instalando pacotes
dotnet add package StackExchange.Redis
dotnet add package Polly
- Criando o Circuit-Breaker com Polly
using Polly;
using Polly.CircuitBreaker;
using StackExchange.Redis;
public static class RedisCircuitBreaker
{
public static AsyncCircuitBreakerPolicy Create()
{
return Policy
.Handle<RedisConnectionException>()
.Or<RedisTimeoutException>()
.CircuitBreakerAsync(
exceptionsAllowedBeforeBreaking: 5,
durationOfBreak: TimeSpan.FromSeconds(30),
onBreak: (ex, ts) =>
{
Console.WriteLine($"[REDIS] Circuit OPEN por {ts.TotalSeconds}s");
},
onReset: () =>
{
Console.WriteLine("[REDIS] Circuit RESET");
},
onHalfOpen: () =>
{
Console.WriteLine("[REDIS] Circuit HALF-OPEN");
}
);
}
}
- Serviço Redis com Circuit Breaker + Fallback
public class RedisCacheService
{
private readonly IDatabase _redis;
private readonly AsyncCircuitBreakerPolicy _circuitBreaker;
private readonly IMemoryCache _memoryCache;
public RedisCacheService(
IConnectionMultiplexer redis,
IMemoryCache memoryCache)
{
_redis = redis.GetDatabase();
_memoryCache = memoryCache;
_circuitBreaker = RedisCircuitBreaker.Create();
}
public async Task<string?> GetAsync(string key)
{
try
{
return await _circuitBreaker.ExecuteAsync(async () =>
{
return await _redis.StringGetAsync(key);
});
}
catch (BrokenCircuitException)
{
// fallback
return _memoryCache.Get<string>(key);
}
}
public async Task SetAsync(string key, string value)
{
try
{
await _circuitBreaker.ExecuteAsync(async () =>
{
await _redis.StringSetAsync(key, value, TimeSpan.FromMinutes(5));
});
// mantém cache local atualizado
_memoryCache.Set(key, value, TimeSpan.FromMinutes(5));
}
catch (BrokenCircuitException)
{
// ignora Redis, salva só em memória
_memoryCache.Set(key, value, TimeSpan.FromMinutes(1));
}
}
}
- Registro no DI
builder.Services.AddSingleton<IConnectionMultiplexer>(
ConnectionMultiplexer.Connect("localhost:6379"));
builder.Services.AddMemoryCache();
builder.Services.AddSingleton<RedisCacheService>();
Degradação graciosa
-
Conceito de Degradação Graciosa: 🔗 Clique aqui
-
Foi aplicado um
rate limiting adaptativopara que em situações onde a aplicação esteja consumindo muito recurso, ela mesmo aplica a sua regra de limitação baseada na condição atual. Limitar todos por causa de poucos é algo que devemos evitar. No exemplo abaixo será feito baseado no tipo do usuário:
Exemplo em .NET
builder.Services.AddRateLimiter(options =>
{
options.AddPolicy("adaptive", context =>
{
var isPremium =
context.User?.Claims.Any(c =>
c.Type == "plan" && c.Value == "premium") == true;
var limit = isPremium ? 500 : 100;
return RateLimitPartition.GetFixedWindowLimiter(
partitionKey: context.User.Identity?.Name
?? context.Connection.RemoteIpAddress?.ToString()
?? "anonymous",
factory: _ => new FixedWindowRateLimiterOptions
{
PermitLimit = limit,
Window = TimeSpan.FromMinutes(1)
});
});
});
/*-----------*/
app.MapGet("/api/data", () => "ok")
.RequireRateLimiting("adaptive");