Saltar al contenido principal

Limitador de Tasa (Rate Limiter)

La política de limitador de tasa controla cuántas solicitudes pueden realizarse dentro de una ventana de tiempo, protegiendo los servicios downstream de ser sobrecargados.

Algoritmos

AlgoritmoDescripción
TokenBucketUn cubo se llena con tokens con el tiempo; cada solicitud consume un token
SlidingWindowCuenta solicitudes en una ventana de tiempo deslizante

Token Bucket

var policy = ResiliencePolicy.Create()
.RateLimiter(opts =>
{
opts.Algorithm = RateLimiterAlgorithm.TokenBucket;
opts.BucketCapacity = 100; // máx. burst
opts.TokensPerInterval = 10; // tasa de reposición
opts.ReplenishmentInterval = TimeSpan.FromSeconds(1); // cada segundo
})
.Build();

Atajo (usa token bucket con valores por defecto):

var policy = ResiliencePolicy.Create()
.RateLimiter(bucketCapacity: 100)
.Build();

Sliding Window

var policy = ResiliencePolicy.Create()
.RateLimiter(opts =>
{
opts.Algorithm = RateLimiterAlgorithm.SlidingWindow;
opts.PermitLimit = 100; // máx. solicitudes por ventana
opts.Window = TimeSpan.FromMinutes(1); // duración de la ventana
})
.Build();

Manejo de Rechazo

try
{
var result = await policy.ExecuteAsync<string>(async ct =>
await _api.CallAsync(ct));
}
catch (RateLimitExceededException ex)
{
_logger.LogWarning("Límite de tasa excedido (Algoritmo: {Algorithm})", ex.Algorithm);
return Results.StatusCode(429);
}

Casos de Uso

// API de GitHub: 5000 solicitudes/hora
var githubPolicy = ResiliencePolicy.Create()
.RateLimiter(opts =>
{
opts.Algorithm = RateLimiterAlgorithm.SlidingWindow;
opts.PermitLimit = 5000;
opts.Window = TimeSpan.FromHours(1);
})
.Build();

Rate Limiting Particionado (por usuario / IP)

Por defecto el rate limiter cuenta todas las llamadas a un tipo de comando en conjunto. Si el Usuario A hace 8 requests y el Usuario B hace 3, la llamada número 11 falla — sin importar quién la generó.

Usá PartitionKeyResolver para darle a cada usuario su propio contador independiente:

services.AddResiliencePolicy<LoginCommand>(req =>
ResiliencePolicy.Create()
.RateLimiter(opts =>
{
opts.Algorithm = RateLimiterAlgorithm.SlidingWindow;
opts.PermitLimit = 5;
opts.Window = TimeSpan.FromMinutes(1);
// cada email tiene su propio contador
opts.PartitionKeyResolver = r => ((LoginCommand)r).Email;
})
.Build());

Con un resolver configurado:

  • Usuario A alcanza su límite → bloqueado
  • Usuario B no se ve afectado y puede seguir haciendo requests

Ejemplos de clave de partición

// Por ID de usuario
opts.PartitionKeyResolver = r => ((MyCommand)r).UserId;

// Por IP (cuando la IP es parte del comando)
opts.PartitionKeyResolver = r => ((MyCommand)r).ClientIp;

// Por tenant
opts.PartitionKeyResolver = r => ((MyCommand)r).TenantId;
nota

PartitionKeyResolver requiere que el request sea despachado a través de ResilienceBehavior (integración estándar con Vali-Mediator). Cada clave única crea una instancia independiente de RateLimiterState gestionada internamente.