Rate Limiting e Throttling em Java: da teoria ao código de produção
Imagine: seu novo microsserviço em Java acabou de se firmar, quando de repente uma avalanche de requisições o atinge. Não são invasores, não — apenas um cliente descuidado esqueceu de colocar um atraso em seu script. Resultado: o banco de dados cai, o servidor sufoca e os usuários (incluindo os honestos) recebem 502 Bad Gateway. Familiar?
Hoje vamos falar sobre como evitar isso. O tema da nossa conversa é rate limiting e throttling em Java. Não são apenas termos da moda do mundo DevOps, mas sua primeira linha de defesa para qualquer API pública. Vamos analisar a diferença entre "limitação de velocidade" e "estrangulamento", explorar algoritmos populares e, o mais importante, escrever código real.
Se você programa em Java e quer que seu serviço permaneça vivo sob qualquer carga — está no lugar certo. Vamos lá!
Rate Limiting vs Throttling: qual a diferença?
Frequentemente esses conceitos são confundidos, embora resolvam problemas diferentes, embora relacionados.
- Rate Limiting — é uma barreira rígida. "No máximo 10 requisições por segundo de um único IP". Excedeu o limite? Receba 429 Too Many Requests. Justo.
- Throttling — é um mecanismo mais suave. O serviço não bloqueia a requisição, mas atrasa seu processamento. Por exemplo, você coloca a requisição em uma fila e a processa a uma velocidade fixa.
Em projetos reais, throttling em Java é frequentemente implementado através de pools de threads (ThreadPoolExecutor) com capacidade limitada, enquanto rate limiting é feito com contadores, caches e filtros intermediários.
Algoritmos de Rate Limiting: escolhendo sua arma
Antes de escrever código, vamos escolher uma estratégia. Aqui estão os três algoritmos mais populares para limitação de requisições em Java.
1. Token Bucket (Balde de Tokens)
Clássico. Temos um "balde" no qual N novos tokens pingam a cada segundo. Para executar uma requisição, é preciso pegar um token. Se o balde estiver vazio — a requisição é rejeitada. O algoritmo lida muito bem com picos repentinos de tráfego, permitindo "pegar" tokens adiantados (até o tamanho do balde).
2. Sliding Window (Janela Deslizante)
Um método mais preciso. Em vez de contar requisições em um segundo calendário, deslizamos uma janela de tempo. Por exemplo, "no máximo 100 requisições nos últimos 60 segundos". Isso evita o "pico" no início de cada intervalo.
3. Fixed Window (Janela Fixa)
O mais simples, mas perigoso. Reiniciamos o contador a cada N segundos. Problema: no final da janela você pode fazer mil requisições, e no início da próxima — mais mil. Não é adequado para sistemas críticos.
Vamos focar nos dois primeiros — eles oferecem o melhor equilíbrio entre desempenho e precisão.
Escrevendo código: Rate Limiter feito por você
Vamos implementar um rate limiter em Java usando o algoritmo Token Bucket. Este é um ótimo exemplo para entender como os sistemas de limitação funcionam internamente.
import java.util.concurrent.ConcurrentHashMap;import java.util.concurrent.TimeUnit;import java.util.concurrent.atomic.AtomicLong;
public class TokenBucketRateLimiter {
private final long maxTokens; // Tamanho máximo do balde private final long refillRate; // Tokens 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