Rate Limiting e Throttling em Go: como proteger sua API e não enlouquecer
\n\nImagine: você escreveu uma API perfeita em Go. Ela voa, processa requisições em milissegundos, e de repente — bum! — uma enxurrada de requisições atinge o servidor. Pode ser um bot, um bug no cliente, ou apenas o hype em torno do seu produto. Sem proteção, o servidor cai, os usuários vão embora, e você vai passar a madrugada analisando logs.
\n\nFamiliar? Então rate limiting (limitação de velocidade) e throttling (estrangulamento) são seus novos melhores amigos. Neste artigo no pythonlib.ru, vamos abordar como implementá-los em Go — uma linguagem criada para sistemas de alta carga. Sem enrolação, apenas código vivo e casos reais.
\n\nRate Limiting vs Throttling: qual a diferença?
\n\nPara começar, vamos organizar os termos. Muitos confundem esses conceitos, mas existe diferença.
\n\n- \n
- Rate Limiting — é uma limitação rígida: "No máximo 100 requisições por minuto". Se o limite for excedido — o servidor responde com 429 Too Many Requests. Tudo justo e estrito. \n
- Throttling — é um ajuste suave: "Vamos te desacelerar, mas não te bloquear". Por exemplo, em vez de responder imediatamente a todas as requisições, as colocamos em uma fila e as processamos a uma velocidade fixa. \n
Em Go, ambas as abordagens são implementadas elegantemente graças às goroutines e canais. Vamos ver exemplos concretos.
\n\nO rate limiter mais simples com canais
\n\nA maneira mais básica de limitar a velocidade é usar um ticker do pacote time. É como um metrônomo: cada "tique" permite uma requisição.
package main\n\nimport (\n "fmt"\n "time"\n)\n\nfunc main() {\n // Cria um canal que enviará tokens a cada 200 ms\n limiter := time.Tick(200 * time.Millisecond)\n\n // Simula 10 requisições\n for i := 1; i <= 10; i++ {\n <-limiter // Aguarda a permissão\n fmt.Printf("Requisição %d processada em %s\\n", i, time.Now().Format("15:04:05.000"))\n }\n}\n\nO que acontece aqui? time.Tick cria um canal que envia um valor a cada 200 milissegundos. Nossa goroutine é bloqueada em <-limiter até receber um token. Resultado: exatamente 5 requisições por segundo, sem variações.
Desvantagem: se houver menos requisições do que ticks, apenas esperamos. Ineficiente? Sim. Mas para cenários simples — é ideal.
\n\nBurst-limiting com buffer
\n\nE se precisarmos permitir "picos" de tráfego? Por exemplo, permitir 10 requisições instantaneamente e depois limitar a 5 por segundo. Para isso, usamos um canal com buffer — o clássico Token Bucket.
\n\npackage main\n\nimport (\n "fmt"\n "time"\n)\n\nfunc main() {\n // Canal com buffer para 5 tokens\n tokens := make(chan struct{}, 5)\n\n // Goroutine de preenchimento: adiciona um token a cada 200 ms\n go func() {\n ticker := time.NewTicker(200 * time.Millisecond)\n defer ticker.Stop()\n for range ticker.C {\n select {\n case tokens <- struct{}{}:\n default:\n // Canal cheio — não faz nada\n }\n }\n }()\n\n // Simula 15 requisições\n for i := 1; i <= 15; i++ {\n <-tokens // Pega um token (espera se vazio)\n fmt.Printf("Requisição %d processada em %s\\n", i, time.Now().Format("15:04:05.000"))\n time.Sleep(50 * time.Millisecond) // Pequeno atraso entre requisições\n }\n}\n\nAs primeiras 5 requisições passarão instantaneamente (buffer cheio), e depois — uma a cada 200 ms. Esse padrão é ideal para APIs onde o cliente pode começar a enviar muitas requisições de repente (por exemplo, após uma reinicialização).
\n\nThrottling com WaitGroup e Worker Pool
\n\nAgora vamos para o throttling. Suponha que temos uma fila de tarefas (por exemplo, envio de e-mail marketing). Não queremos bloquear o cliente, mas também não queremos sobrecarreg