Rate Limiting y Throttling en Java: de la teoría al código de producción
Imagina: tu nuevo microservicio en Java acaba de ponerse en marcha cuando, de repente, una avalancha de solicitudes cae sobre él. No son atacantes, no — solo un cliente descuidado que olvidó poner una pausa en su script. Resultado: la base de datos colapsa, el servidor se ahoga y los usuarios (incluyendo los responsables) reciben un 502 Bad Gateway. ¿Te suena?
Hoy hablaremos de cómo evitar esto. El tema de nuestra conversación es el rate limiting y throttling en Java. No son solo términos de moda del mundo DevOps, sino tu primera línea de defensa para cualquier API pública. Analizaremos en qué se diferencia la "limitación de velocidad" del "estrangulamiento", revisaremos los algoritmos más populares y, lo más importante, escribiremos código real.
Si programas en Java y quieres que tu servicio se mantenga vivo bajo cualquier carga — estás en el lugar correcto. ¡Allá vamos!
Rate Limiting vs Throttling: ¿cuál es la diferencia?
A menudo estos conceptos se confunden, aunque resuelven problemas diferentes, aunque relacionados.
- Rate Limiting — es una barrera estricta. "No más de 10 solicitudes por segundo desde una misma IP". ¿Superaste el límite? Recibe un 429 Too Many Requests. Es justo.
- Throttling — es un mecanismo más suave. El servicio no bloquea la solicitud, sino que ralentiza su procesamiento. Por ejemplo, pones la solicitud en una cola y la procesas a una velocidad fija.
En proyectos reales, el throttling en Java a menudo se implementa mediante pools de hilos (ThreadPoolExecutor) con capacidad limitada, mientras que el rate limiting se implementa con contadores, cachés y filtros intermedios.
Algoritmos de Rate Limiting: elige tu arma
Antes de escribir código, elijamos una estrategia. Estos son los tres algoritmos más populares para la limitación de solicitudes en Java.
1. Token Bucket (Cubo de fichas)
Un clásico. Tenemos un "cubo" al que cada segundo caen N fichas nuevas. Para ejecutar una solicitud, necesitas tomar una ficha. Si el cubo está vacío, la solicitud se rechaza. El algoritmo maneja muy bien los picos repentinos de tráfico, permitiendo "tomar prestadas" fichas por adelantado (hasta el tamaño del cubo).
2. Sliding Window (Ventana deslizante)
Un método más preciso. En lugar de contar las solicitudes por segundo calendario, movemos una ventana de tiempo. Por ejemplo, "no más de 100 solicitudes en los últimos 60 segundos". Esto evita el "pico" al inicio de cada intervalo.
3. Fixed Window (Ventana fija)
El más simple, pero peligroso. Reiniciamos el contador cada N segundos. Problema: al final de la ventana puedes hacer mil solicitudes, y al inicio de la siguiente, otras mil. No es adecuado para sistemas críticos.
Nos centraremos en los dos primeros: ofrecen el mejor equilibrio entre rendimiento y precisión.
Escribiendo código: Rate Limiter hecho por nosotros mismos
Implementemos un rate limiter en Java usando el algoritmo Token Bucket. Es un excelente ejemplo para entender cómo funcionan los sistemas de limitación desde dentro.
import java.util.concurrent.ConcurrentHashMap;import java.util.concurrent.TimeUnit;import java.util.concurrent.atomic.AtomicLong;
public class TokenBucketRateLimiter {
private final long maxTokens; // Tamaño máximo del cubo private final long refillRate; // Fichas por segundo private final ConcurrentHashMap<String, Bucket> buckets = new ConcurrentHashMap<>();
public TokenBucketRateLimiter(long maxTokens, long refillRate) { this.maxTokens = maxTokens; this.refillRate = refillRate; }
public boolean tryAcquire(String clientId) { Bucket bucket = buckets.computeIfAbsent(clientId, k -> new Bucket(maxTokens)); return bucket.tryConsume(); }
private class Bucket { private final AtomicLong tokens; private volatile long lastRefillTimestamp;
Bucket(long cap