Quando sua API começa a sufocar: Introdução ao rate limiting
Imagine a situação: você escreveu um servidor perfeito em C++. Rápido, eficiente, usando todos os núcleos da CPU. Você o coloca em produção e... cinco minutos depois ele cai. O que aconteceu? Descobriu-se que algum script malicioso decidiu "testar" sua API, enviando milhares de requisições por segundo.
Familiar? Se não, você teve sorte. Mas mais cedo ou mais tarde, todo desenvolvedor enfrenta a necessidade de controlar a carga. É aqui que entram em cena o rate limiting e o throttling — dois mecanismos poderosos que salvam servidores de sobrecargas e garantem uma distribuição uniforme de recursos.
Neste artigo, vamos entender como implementar esses mecanismos em C++. Você aprenderá não apenas a teoria, mas também receberá código funcional que pode usar imediatamente em seus projetos. Vamos lá!
Rate Limiting vs Throttling: Qual a diferença?
Antes de mergulharmos no código, vamos diferenciar claramente esses dois conceitos. Muitos os confundem, mas a diferença existe e é crítica.
Rate Limiting (Limitação de Taxa)
É uma "barreira". Você diz: "Em um segundo — no máximo 10 requisições. Tudo que exceder — é bloqueado (ou vai para a fila)". O rate limiting protege sua API contra requisições em avalanche, ataques DDoS e picos aleatórios de tráfego.
Throttling (Estrangulamento)
É um "controlador de volume". Você não bloqueia as requisições completamente, mas atrasa o processamento delas. Por exemplo, se um cliente envia 100 requisições por segundo, você as processa, mas com um atraso de 10 ms entre cada uma. O throttling é uma forma mais "educada" de gerenciar a carga, não descarta dados, apenas suaviza os picos.
Em projetos reais, essas técnicas são frequentemente combinadas: primeiro o throttling para suavizar, e quando o limite crítico é excedido, o rate limiting com retorno do erro 429 (Too Many Requests).
Algoritmos de rate limiting em C++
Existem vários algoritmos clássicos. Vamos abordar os dois mais populares e eficientes: Token Bucket e Sliding Window. Ambos são facilmente implementados em C++ e fornecem excelentes resultados.
1. Token Bucket (Balde de Tokens)
Imagine um balde no qual caem N tokens a cada segundo. Cada requisição consome um token. Se o balde estiver vazio — a requisição é rejeitada. Simples e genial.
Aqui está a implementação em C++ moderno usando std::chrono:
#include <chrono>#include <mutex>#include <atomic>
class TokenBucket {public: TokenBucket(size_t max_tokens, double refill_rate) : max_tokens_(max_tokens) , refill_rate_(refill_rate) , current_tokens_(max_tokens) , last_refill_(std::chrono::steady_clock::now()) {}
bool try_consume() { std::lock_guard<std::mutex> lock(mutex_); // Calcula quantos tokens se acumularam desde a última vez auto now = std::chrono::steady_clock::now(); auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - last_refill_); // Reabastece o balde double tokens_to_add = elapsed.count() * refill_rate_ / 1000.0; current_tokens_ = std::min(max_tokens_, current_tokens_ + tokens_to_add); last_refill_ = now; // Tenta consumir um token if (current_tokens_ > 0) { --current_tokens_; return true; } return false; }
private: std::mutex mutex_; size_t max_tokens_; // Capacidade máxima do balde double refill_rate_; // Tokens por segundo double current_tokens_; // Quantidade atual de tokens std::chrono::steady_clock::time_point last_refill_;};Uso:
TokenBucket limiter(10, 1.0); // 10 tokens, recarga de 1 token/seg
// No manipulador de requisição:if (limiter.tr