Rate Limiting Distribuído em Microsserviços com Token Bucket e Redis Cluster
Aprenda a projetar controle de tráfego distribuído em sistemas de microsserviços usando o algoritmo token bucket e instâncias coordenadas de Redis Cluster para mitigar sobrecargas e ataques de negação de serviço.
Resumo
- O algoritmo token bucket permite picos de tráfego controlados enquanto garante uma média constante de requisições por segundo.
- A arquitetura de Redis Cluster evita gargalos de ponto único de falha ao particionar os dados de controle entre múltiplos nós.
- Scripts em Lua executados atomicamente no Redis eliminam condições de corrida durante a verificação e o consumo de tokens.
- Estratégias de fallback local garantem que o sistema continue operacional mesmo quando o cluster de cache sofre indisponibilidade.
- A chave composta por identificador de usuário e janela de tempo previne o esgotamento global incorreto de recursos compartilhados.
O Desafio de Proteger Microsserviços contra Tráfego Excessivo
Em arquiteturas modernas baseadas em microsserviços, a escalabilidade elástica é uma benção e uma maldição. Enquanto adicionamos novas instâncias de servidores facilmente para lidar com picos de acesso, nossos bancos de dados relacionais, filas de mensagens e APIs de terceiros continuam tendo limites estritos de capacidade. É exatamente aqui que entra o rate limiting, ou limitação de taxa: uma técnica fundamental de engenharia de software que age como um segurança rigoroso na porta de uma festa concorrida, controlando exatamente quantas requisições cada cliente pode fazer em um determinado intervalo de tempo para evitar falhas em cascata.
Quando temos uma aplicação monolítica rodando em um único servidor, contar acessos é uma tarefa trivial mantida na memória RAM local. No entanto, quando distribuímos nossa aplicação em dezenas de containers Docker orquestrados por Kubernetes, o cenário muda drasticamente. Se um usuário mal-intencionado ou um cliente com um bug de software envia milhares de requisições por segundo, essas chamadas podem ser distribuídas aleatoriamente por diferentes nós da nossa infraestrutura. Sem um mecanismo centralizado e coordenado de contagem, cada servidor achará erroneamente que o tráfego está baixo, permitindo que a cota total de requisições seja ultrapassada em várias vezes.
Compreendendo o Algoritmo Token Bucket na Prática
Existem várias formas matemáticas de limitar tráfego, mas o token bucket, ou balde de tokens, destaca-se como o padrão ouro da indústria por sua flexibilidade incomparável. Imagine um balde que armazena fichas ou tokens até um limite máximo predefinido. Uma torneira invisível despeja novas fichas nesse balde a uma taxa constante, por exemplo, dez fichas por segundo. Cada vez que um cliente faz uma requisição para a nossa API, o sistema tenta retirar uma ficha do balde correspondente àquele cliente. Se o balde tiver fichas suficientes, a requisição é autorizada imediatamente e a ficha é descartada. Se o balde estiver completamente vazio, a requisição é rejeitada com o famoso código HTTP 429 Too Many Requests.
Na prática, isso significa que o token bucket resolve um dos maiores problemas dos contadores tradicionais: ele tolera rajadas legítimas de tráfego. Se um aplicativo móvel precisa carregar dez imagens simultaneamente ao abrir, o balde cheio permite que todas passem de uma vez, desde que a média de longo prazo respeite a taxa de reposição. Em contrapartida, algoritmos mais rígidos rejeitariam o acesso logo após a primeira requisição excedente daquele microssegundo. Para implementar isso de forma eficiente em sistemas distribuídos, precisamos de um armazenamento de alta velocidade que suporte concorrência extrema sem perder a precisão temporal.
Arquitetura de Redis Cluster para Alta Disponibilidade
Para sincronizar o estado dos baldes de tokens entre dezenas de servidores de aplicação, precisamos de um banco de dados em memória ultrarrápido, e o Redis surge como a escolha natural da comunidade. Mais do que uma simples instância isolada, o Redis Cluster oferece particionamento automático de dados e replicação nativa, dividindo as chaves em até 16.384 slots hash distribuídos entre vários nós. Isso significa que, mesmo se um nó do cluster falhar durante um pico de tráfego, o restante da infraestrutura continua operacional, garantindo a resiliência exigida em ambientes de missão crítica.
No entanto, operar contadores distribuídos no Redis apresenta armadilhas sutis de concorrência. Se dois servidores de aplicação lerem o número de tokens restantes no mesmo milissegundo, ambos calcularem que ainda há espaço e decrementarem o valor separadamente, teremos uma condição de corrida que corrompe a precisão do limite. Para blindar nosso sistema contra esse tipo de falha, utilizamos scripts em Lua executados diretamente no servidor Redis. Como o Redis processa comandos e scripts de forma completamente atômica, garantimos que a leitura, a verificação e a atualização do balde aconteçam como uma transação única e indivisível.
Implementação Prática do Algoritmo com Scripts Lua
Abaixo apresentamos um exemplo funcional de script Lua projetado para rodar no Redis, implementando a lógica de reposição gradual e consumo de tokens por chave de cliente. Este script calcula o tempo decorrido desde a última requisição, adiciona os novos tokens gerados com base na taxa configurada e valida se há saldo suficiente para autorizar a transação atual.
local key = KEYS[1]local now = tonumber(ARGV[1])local capacity = tonumber(ARGV[2])local fill_rate = tonumber(ARGV[3])local requested = tonumber(ARGV[4])local data = redis.call('HMGET', key, 'tokens', 'last_updated')local tokens = tonumber(data[1])local last_updated = tonumber(data[2])if not tokens then tokens = capacity last_updated = nowelse local delta = math.max(0, now - last_updated) tokens = math.min(capacity, tokens + delta * fill_rate)endlocal allowed = 0if tokens >= requested then tokens = tokens - requested allowed = 1endredis.call('HMSET', key, 'tokens', tokens, 'last_updated', now)redis.call('EXPIRE', key, math.ceil(capacity / fill_rate))return {allowed, tokens}Para integrar esse script na aplicação em backend, enviamos os parâmetros dinâmicos a cada requisição HTTP recebida no gateway da API. Se o retorno indicar que a operação foi permitida, o fluxo prossegue normalmente para os microsserviços internos. Caso contrário, interrompemos a execução imediatamente, economizando recursos computacionais valiosos dos servidores de backend e protegendo o ecossistema contra sobrecargas destrutivas.
Estratégias de Fallback e Tratamento de Falhas em Produção
Nenhum sistema distribuído é imune a quedas de rede ou falhas temporárias de infraestrutura, e contar cegamente com o Redis Cluster pode se transformar em um ponto único de falha catastrófica se o cache ficar inacessível. Quando o cluster de Redis sofre uma interrupção, o pior cenário possível é fazer com que todas as requisições dos clientes falhem devido à incapacidade de validar o rate limiting. Para mitigar esse risco operacional, arquiteturas maduras implementam mecanismos inteligentes de fail-open ou fallback local em memória.
Na prática, isso significa que se a chamada ao Redis retornar um erro de timeout ou conexão recusada após um limite estrito de milissegundos, o middleware de limitação assume um comportamento permissivo temporário ou recorre a um contador heurístico em memória local. Essa decisão garante a continuidade dos negócios para os usuários legítimos durante incidentes de infraestrutura, enquanto alertas automáticos notificam a equipe de engenharia para restaurar a saúde do cluster de cache o mais rápido possível.
Considerações Finais sobre Escalabilidade e Resiliência
Implementar um sistema robusto de rate limiting distribuído usando o algoritmo token bucket e Redis Cluster exige um equilíbrio cuidadoso entre precisão matemática, latência de rede e tolerância a falhas. Embora o custo de infraestrutura para manter nós de cache dedicados exista, ele é infinitamente menor do que o prejuízo financeiro e reputacional causado pela indisponibilidade de plataformas inteiras por falta de proteção contra picos de tráfego. Ao adotar scripts atômicos em Lua e estratégias defensivas de fallback, engenheiros conseguem construir ecossistemas de microsserviços altamente resilientes, capazes de absorver tempestades de requisições sem perder a compostura.