Marcio Cunha

Estratégias de Rate Limiting Distribuído com Contadores Atômicos e Redis Cluster

Descubra como projetar um sistema de controle de tráfego distribuído de alta escala utilizando Redis Cluster e operações atômicas para proteger APIs contra sobrecargas.

Marcio Cunha•5 min
Também disponível em:EnglishEspañol
Resumo
  • O Redis Cluster distribui chaves por slots de hash, exigindo que chaves do mesmo usuário fiquem na mesma instância usando chaves de hash.
  • Comandos EVAL e scripts Lua garantem atomicidade, evitando que requisições simultâneas corrompam a contagem de acessos.
  • A estratégia de janela deslizante oferece precisão superior em relação à janela fixa, evitando estouros de limite nas bordas do tempo.
  • Tratar falhas de rede e indisponibilidade de nós evita que o mecanismo de proteção bloqueie tráfego legítimo por falhas internas.
  • Políticas de fallback Graceful mantêm o sistema operando mesmo quando o banco de dados em memória sofre degradação temporária.

O desafio de controlar o tráfego em sistemas modernos

Quando uma aplicação web cresce e passa a receber milhões de acessos diários, proteger a infraestrutura contra abusos e ataques de negação de serviço torna-se uma prioridade absoluta. O controle de tráfego, conhecido na engenharia como rate limiting, serve exatamente para impor limites na quantidade de requisições que um cliente pode realizar dentro de um determinado intervalo de tempo. Na prática, isso significa evitar que um único usuário mal-intencionado — ou um script com falha — derrube todos os servidores da empresa por esgotamento de recursos computacionais.

Em ambientes monolíticos tradicionais, essa contagem costuma ser feita na memória local do próprio servidor. No entanto, a arquitetura moderna baseia-se em microsserviços distribuídos por dezenas ou centenas de máquinas na nuvem. Se cada máquina controlar sua própria contagem isoladamente, um cliente poderá multiplicar o volume de requisições simplesmente alternando entre diferentes servidores de atendimento. Resolver esse problema exige centralizar o estado das requisições em um repositório compartilhado de baixíssima latência.

Por que o Redis Cluster se tornou o padrão da indústria

O Redis é um banco de dados em memória extremamente rápido, famoso por processar centenas de milhares de operações por segundo com tempos de resposta na casa dos microssegundos. Quando escalamos para um Redis Cluster, dividimos os dados em múltiplos nós para garantir alta disponibilidade e tolerância a falhas. Cada dado armazenado é direcionado para um dos 16384 slots de hash disponíveis na arquitetura do cluster, permitindo que a carga seja distribuída de maneira equilibrada entre as máquinas disponíveis.

A principal armadilha ao implementar controle de tráfego em um cluster distribuído envolve a movimentação de dados entre nós diferentes. Se o limite de requisições de um usuário depender de chaves armazenadas em nós distintos, o sistema perderá desempenho devido ao custo de comunicação de rede. Para solucionar isso, utilizamos chaves de hash customizadas, conhecidas como hash tags, que forçam o Redis a armazenar todas as informações de um cliente específico no mesmo nó físico do cluster.

Garantindo atomicidade com scripts em Lua

Em sistemas concorrentes, duas requisições que chegam exatamente no mesmo milissegundo podem ler o valor atual de um contador, somar um e reescrevê-lo, causando uma perda de dados conhecida como condição de corrida. Para impedir que isso aconteça sem travar todo o sistema, utilizamos operações atômicas, que são blocos de código executados de forma ininterrupta do início ao fim. No ecossistema Redis, a maneira mais elegante de alcançar essa atomicidade em lógica complexa é através de scripts escritos na linguagem Lua.

O script Lua é enviado diretamente para o servidor Redis, que o executa internamente na mesma thread principal, garantindo que nenhuma outra operação interfira nos dados durante a execução. Na prática, o script lê o timestamp atual, limpa registros antigos se estivermos usando uma janela deslizante, incrementa o contador e verifica se o limite foi ultrapassado, tudo em uma única transação atômica. Isso elimina o tráfego desnecessário de rede entre a aplicação e o banco de dados, entregando uma resposta imediata e totalmente confiável.

local key = KEYS[1]local limit = tonumber(ARGV[1])local current = redis.get(key)if current and tonumber(current) >= limit then    return 0elsedelta = redis.incr(key)if delta == 1 then    redis.expire(key, 60)endreturn 1end

Arquitetura da janela deslizante para máxima precisão

Existem diferentes algoritmos para calcular o limite de tráfego, sendo o mais simples a janela fixa, que zera o contador a cada minuto cheio. O problema da janela fixa é o efeito de pico nas bordas: um usuário pode esgotar todo o seu limite nos últimos segundos do minuto atual e gastar tudo novamente logo no primeiro segundo do minuto seguinte, dobrando a carga permitida na transição. Para resolver essa falha conceitual, engenheiros adotam a abordagem de janela deslizante.

A janela deslizante calcula o fluxo de requisições considerando uma proporção do minuto anterior somada ao minuto atual, suavizando a curva de consumo ao longo do tempo. Implementar essa lógica com contadores atômicos exige armazenar subcontadores ou utilizar estruturas de dados ordenadas no Redis, como os Sorted Sets. Embora exija um pouco mais de processamento por parte do servidor de cache, o ganho em termos de precisão e proteção contra picos repentinos compensa amplamente o custo computacional adicionado.

Tratamento de falhas e estratégias de fallback

Nenhum sistema distribuído é totalmente imune a quedas de rede, reinicializações de nós ou falhas de hardware. Se o cluster Redis inteiro cair por alguns instantes, a aplicação que depende dele não pode simplesmente parar de funcionar ou recusar todas as requisições dos clientes legítimos. É fundamental desenhar estratégias de fallback inteligentes, determinando o comportamento do sistema quando o mecanismo central de controle de tráfego se torna temporariamente inacessível.

Uma abordagem comum consiste em implementar um mecanismo de tolerância a falhas do tipo fail-open, onde, caso o Redis retorne um erro de conexão, a requisição é autorizada a prosseguir enquanto um alerta é disparado para a equipe de operações. Embora isso abra uma brecha temporária para abusos durante a pane, protege a experiência dos usuários comuns contra indisponibilidades totais do serviço. O segredo reside em monitorar constantemente a latência e a taxa de erros do cluster para agir preventivamente antes que ocorra uma pane generalizada.

Considerações finais sobre resiliência e escala

Construir um sistema de controle de tráfego distribuído exige equilibrar precisão matemática, performance de rede e resiliência operacional. O uso inteligente do Redis Cluster aliado a scripts Lua atômicos oferece a robustez necessária para suportar picos extremos de acesso sem comprometer a estabilidade do backend. Mais do que proteger servidores contra sobrecargas, essa arquitetura garante previsibilidade e confiabilidade para toda a plataforma tecnológica.