Limitar APIs: Algoritmos e Implementaciones
Tu API está bajo ataque. No por hackers sofisticados, sino por un simple script que golpea tus endpoints miles de veces por segundo, consumiendo recursos y degradando el servicio para usuarios legítimos. Sin limitación de tasa, un solo cliente con comportamiento incorrecto puede derribar toda tu aplicación. Esta guía explica los algoritmos principales y muestra implementaciones prácticas en Node.js y Nginx para proteger tu API.
Por qué es importante la limitación de tasa
La limitación de tasa controla cuántas solicitudes puede hacer un cliente en una ventana de tiempo determinada. Es tu primera línea de defensa contra:
- Ataques de fuerza bruta en endpoints de inicio de sesión o claves de API
- Denegación de servicio (DoS) por solicitudes excesivas
- Agotamiento de recursos por operaciones costosas como consultas a bases de datos o procesamiento de archivos
- Abuso de niveles gratuitos por scrapers o bots
Más allá de la seguridad, la limitación de tasa asegura un uso justo y te ayuda a hacer cumplir reglas de negocio, como planes de precios escalonados.
Algoritmos de limitación de tasa
Cuatro algoritmos dominan la limitación de tasa en APIs. Cada uno tiene ventajas y desventajas en precisión, uso de memoria y manejo de ráfagas.
1. Ventana fija
Cuenta las solicitudes en intervalos de tiempo fijos (por ejemplo, 100 solicitudes por minuto). Cuando la ventana se reinicia, el contador se reinicia.
- Pros: Simple, bajo consumo de memoria (un contador por cliente).
- Contras: Ráfagas en los límites de la ventana. Un cliente puede enviar 100 solicitudes a las 12:00:59 y otras 100 a las 12:01:00, efectivamente 200 solicitudes en dos segundos.
2. Ventana deslizante
Registra las marcas de tiempo de cada solicitud y cuenta cuántas caen dentro de los últimos N segundos. Esto suaviza las ráfagas.
- Pros: Preciso, sin picos en los límites.
- Contras: Mayor uso de memoria (almacenar marcas de tiempo) o usar un contador de ventana deslizante con promedio ponderado.
3. Token bucket
Un bucket contiene tokens. Los tokens se añaden a una tasa fija. Cada solicitud consume un token. Si el bucket está vacío, la solicitud se deniega.
- Pros: Permite ráfagas hasta el tamaño del bucket, luego aplica la tasa promedio.
- Contras: Requiere almacenar el conteo de tokens y el último tiempo de recarga por cliente.
4. Leaky bucket
Las solicitudes entran en una cola (bucket) y se procesan a una tasa constante. Si la cola está llena, las solicitudes se descartan.
- Pros: Suaviza el tráfico a una tasa constante, ideal para proteger servicios downstream.
- Contras: Añade latencia; no es adecuado para APIs en tiempo real.
| Algoritmo | Manejo de ráfagas | Memoria | Caso de uso |
|---|---|---|---|
| Ventana fija | Pobre | Baja | APIs simples |
| Ventana deslizante | Buena | Media | Propósito general |
| Token bucket | Excelente | Media | APIs con tolerancia a ráfagas |
| Leaky bucket | Ninguno | Media | Suavizar tráfico |
Implementar limitación de tasa en Node.js
Implementaremos un limitador de tasa token bucket usando Express y Redis para estado distribuido. Redis es esencial cuando tienes múltiples instancias de servidor.
Paso 1: Instalar dependencias
npm install express redis
Paso 2: Crear el middleware del limitador de tasa
const redis = require('redis');
const client = redis.createClient();
async function tokenBucketLimiter(req, res, next) {
const key = `rate_limit:${req.ip}`;
const capacity = 10; // max tokens
const refillRate = 1; // tokens per second
const now = Date.now();
const data = await client.hGetAll(key);
let tokens = data.tokens ? parseFloat(data.tokens) : capacity;
let lastRefill = data.lastRefill ? parseInt(data.lastRefill) : now;
// Refill tokens based on elapsed time
const elapsed = (now - lastRefill) / 1000;
tokens = Math.min(capacity, tokens + elapsed * refillRate);
if (tokens < 1) {
return res.status(429).json({ error: 'Too many requests' });
}
tokens -= 1;
await client.hSet(key, {
tokens: tokens.toString(),
lastRefill: now.toString()
});
await client.expire(key, 60); // auto-cleanup
next();
}
app.use(tokenBucketLimiter);
Este middleware verifica y actualiza el conteo de tokens atómicamente. Para producción, usa transacciones de Redis o scripts Lua para evitar condiciones de carrera.
Implementar limitación de tasa en Nginx
Nginx ofrece limitación de tasa integrada con el módulo limit_req. Utiliza un algoritmo leaky bucket.
Paso 1: Definir una zona de límite de tasa
En el bloque http de nginx.conf:
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
Esto crea una zona de 10MB llamada api que permite 10 solicitudes por segundo por IP.
Paso 2: Aplicar el límite
En tu bloque location:
location /api/ {
limit_req zone=api burst=20 nodelay;
proxy_pass http://backend;
}
burst=20 permite ráfagas cortas de hasta 20 solicitudes. nodelay procesa las solicitudes en ráfaga inmediatamente en lugar de ponerlas en cola.
Mejores prácticas para la limitación de tasa en APIs
- Devuelve códigos de estado adecuados: Usa
429 Too Many Requestse incluye el encabezadoRetry-After. - Identifica a los clientes correctamente: Usa claves de API o IDs de usuario en lugar de direcciones IP cuando sea posible, ya que las IP pueden ser compartidas (NAT) o falsificadas.
- Distribuye el estado: Usa Redis o un almacén similar para despliegues con múltiples instancias.
- Registra y monitorea: Rastrea los aciertos del límite de tasa para detectar ataques y ajustar umbrales. Herramientas como el Analizador de Logs de Nginx pueden ayudarte a analizar respuestas 429 e identificar IPs abusivas.
- Comunica los límites: Documenta los límites de tasa en tu documentación de API e incluye encabezados como
X-RateLimit-Limit,X-RateLimit-Remaining.
Preguntas frecuentes
¿Cuál es la diferencia entre limitación de tasa y throttling?
La limitación de tasa bloquea las solicitudes que superan un umbral, mientras que el throttling las ralentiza (por ejemplo, mediante colas o retrasos). La limitación de tasa es binaria; el throttling es gradual.
¿Qué algoritmo de limitación de tasa debería usar?
Para la mayoría de las APIs, token bucket ofrece un buen equilibrio: permite ráfagas pero aplica una tasa promedio. Si necesitas un suavizado estricto, usa leaky bucket. Por simplicidad, la ventana fija funciona para APIs de bajo tráfico.
¿Cómo manejo la limitación de tasa para usuarios autenticados vs. anónimos?
Aplica límites más estrictos a usuarios anónimos (por ejemplo, por IP) y límites más generosos a usuarios autenticados (por ejemplo, por clave de API). También puedes implementar límites escalonados según planes de suscripción.
¿Listo para analizar el tráfico de tu API? Usa nuestro Analizador de Logs de Nginx para parsear logs, detectar violaciones de límites de tasa y optimizar tus umbrales.