Rate Limiting Distribuído com Redis e Scripts Lua
Proteja suas APIs contra sobrecarga utilizando a atomicidade dos scripts Lua no Redis. Entenda como implementar um controle de tráfego resiliente em arquiteturas distribuídas.
Resumo
- A atomicidade do Redis garante que a contagem de requisições seja precisa mesmo em ambientes com múltiplos servidores.
- Scripts Lua eliminam condições de corrida ao executar toda a lógica de verificação e incremento em uma única operação indivisível.
- O uso da estratégia Token Bucket permite suavizar picos de tráfego mantendo uma taxa média constante de processamento.
- A centralização do controle de limite em memória reduz drasticamente a latência em comparação a verificações persistentes em bancos de dados tradicionais.
- A resiliência do sistema depende do tratamento correto de erros de conexão e de estratégias de fail-open ou fail-close adequadas para cada negócio.
O desafio da sobrecarga em sistemas distribuídos
Manter uma API saudável exige controle rigoroso sobre a quantidade de requisições que cada cliente pode realizar. Quando um sistema é distribuído entre diversos servidores, o desafio cresce: como garantir que um limite global seja respeitado sem criar gargalos de sincronização? O Rate Limiting (ou limitação de taxa) atua como um porteiro inteligente, descartando excessos antes que eles sobrecarreguem o banco de dados ou os serviços de backend.
A atomicidade como requisito de integridade
O maior inimigo do controle de tráfego é a condição de corrida, que ocorre quando duas requisições simultâneas tentam ler e atualizar um contador ao mesmo tempo. Se o processo não for atômico (ou seja, se não acontecer em um único passo indissolúvel), o contador pode ser corrompido, permitindo que usuários mal-intencionados ou bots burlem as restrições. O Redis, por ser single-threaded em seu núcleo, oferece a base ideal para essa atomicidade.
Scripts Lua: o motor de execução
Ao utilizar scripts Lua no Redis, enviamos toda a lógica de decisão para dentro do próprio servidor de cache. Isso significa que o servidor de aplicação não precisa fazer várias viagens de ida e volta (round-trips) para verificar o estado e atualizar o contador. O script é executado como uma operação bloqueante única dentro do Redis, eliminando qualquer interferência externa.
Implementação do algoritmo Token Bucket
O algoritmo Token Bucket funciona como um balde que é preenchido com tokens a uma taxa constante, onde cada requisição consome um token. Se o balde estiver vazio, a requisição é negada. No Redis, armazenamos o timestamp da última atualização e a quantidade atual de tokens, calculando o preenchimento proporcional ao tempo decorrido desde a última ação do usuário.
local key = KEYS[1] local limit = tonumber(ARGV[1]) local window = tonumber(ARGV[2]) local now = tonumber(ARGV[3]) local bucket = redis.call('hmget', key, 'tokens', 'last_refill') local tokens = tonumber(bucket[1]) or limit local last_refill = tonumber(bucket[2]) or now local elapsed = math.max(0, now - last_refill) local refill = math.floor(elapsed * (limit / window)) tokens = math.min(limit, tokens + refill) if tokens > 0 then redis.call('hmset', key, 'tokens', tokens - 1, 'last_refill', now) return 1 else return 0 endConsiderações de arquitetura e resiliência
Um sistema robusto não deve falhar completamente se o Redis estiver indisponível. Implementar um padrão de 'fail-open' garante que, em caso de erro no cache, as requisições sejam processadas normalmente, evitando uma interrupção total do serviço. Monitorar o uso de memória do Redis é igualmente crítico, visto que chaves de controle com tempo de expiração muito longo podem levar ao esgotamento de RAM.
Conclusão
A combinação de Redis com Lua transforma uma tarefa complexa de sincronização distribuída em uma operação de alta performance. Ao mover a lógica de autorização para a camada de cache, ganhamos latência mínima e precisão matemática. A escolha entre estratégias de limite depende da sensibilidade do seu sistema a picos versus uma média constante, mas a estrutura aqui apresentada serve como alicerce para escalar com segurança.