Pular para conteúdo

API Gateway e Rate Limiting

O BoxOffice tem nove serviços. Sem um gateway, o app mobile precisaria conhecer nove endereços, cada serviço validaria JWTs por conta própria (com nove bugs ligeiramente diferentes), cada serviço implementaria o seu próprio rate limit, e o script de um fã martelando POST /api/holds 500 vezes por segundo iria direto para inventory. O API gateway é a porta de entrada única que resolve tudo isso num só lugar — e é também o lugar onde muitas arquiteturas, sem alarde, criam um gargalo ou um segundo monólito.

Um API gateway é um reverse proxy L7 especializado em APIs: é o ponto de entrada único dos clientes, roteia cada requisição para o serviço certo e aplica políticas transversais — autenticação, rate limits, cotas, transformações, observabilidade — antes que a requisição chegue a qualquer código de negócio1. Ele é o componente apigw da paleta.


O que um gateway faz

flowchart LR
    app([App mobile]) --> gw
    web([App web]) --> gw
    partner([Cliente da API de parceiros]) --> gw
    subgraph gw [API gateway]
        direction TB
        a["Autenticar: JWT / API key"] --> r["Rate limit e cota"]
        r --> t["Rotear + transformar"]
        t --> o["Log, métricas, trace"]
    end
    gw --> catalog[catalog]
    gw --> inventory[inventory]
    gw --> order[order]
    gw --> assistant[assistant]
Responsabilidade O que significa Exemplo no BoxOffice
Roteamento mapear host/path/método para um serviço e uma versão /api/holds/** → inventory, /api/v2/orders/** → order v2
Offload de autenticação (authN) validar a assinatura e a expiração do JWT, ou a API key, uma única vez rejeitar tokens expirados antes que cheguem a qualquer serviço; repassar X-User-Id adiante
Autorização grossa (authZ) regras por rota: escopos, papéis /api/admin/** exige o papel admin; regras finas ("este usuário pode cancelar este pedido?") ficam no serviço
Rate limiting e cotas requisições por segundo por usuário/IP/key; cotas mensais por plano 10 req/s por fã, rajadas de 20; planos de parceiros de 1 M chamadas/mês
Transformação de requisição/resposta renomear headers, remover campos internos, converter protocolos (REST ↔ gRPC) expor a API gRPC de inventory como REST para o app web
Agregação uma chamada do cliente se espalha por vários serviços e junta as respostas GET /api/events/42/page devolve evento + local + disponibilidade
Cache guardar respostas de GET por pouco tempo detalhes do evento por 30 s
Observabilidade access logs, métricas RED por rota, início do trace distribuído p99 por rota, header traceparent injetado
Resiliência timeouts, retries, circuit breakers por rota timeout de 2 s para inventory; sem retries em POST
Produtos de API API keys, portal do desenvolvedor, planos, cobrança por uso parceiros de revenda de ingressos

A palavra-chave é transversal: preocupações que são as mesmas para todos os serviços. Tudo o que depende do negócio — preços, alocação de assentos, regras de reembolso — pertence a um serviço, não à configuração do gateway.

No código do BoxOffice: as rotas, os timeouts e o retry do gateway (lab 4)

O Hands-on 4 — Gateway coloca um Spring Cloud Gateway atrás da borda do lab 3. A configuração dele é quase a tabela acima inteira em poucas linhas: roteamento por path (/auth/**, /catalog/**); lb://catalog, que balanceia entre as três réplicas e pula qualquer uma cuja readiness não esteja UP (verificada a cada 5 s); um timeout de conexão de 1 s e de resposta de 5 s para todos os serviços; e um filtro Retry limitado a GET, para que um retry nunca repita uma compra. A autenticação — validar o JWT que auth assina — está em SecurityConfig.java, na mesma página.

services/gateway/src/main/resources/application.yaml (routes and resilience)
cloud:
  gateway:
    server:
      webflux:
        httpclient:
          connect-timeout: 1000          # ms to open a connection to a service
          response-timeout: 5s           # never wait forever for a service
        default-filters:
          # retry idempotent GETs once on another instance when a connection fails
          - name: Retry
            args:
              retries: 1
              methods: GET
              series: SERVER_ERROR
              exceptions: java.io.IOException, java.util.concurrent.TimeoutException
        routes:
          - id: auth
            uri: ${AUTH_URL:http://auth:8081}
            predicates:
              - Path=/auth/**
          - id: catalog
            uri: lb://catalog              # "lb": pick one instance of "catalog" per request
            predicates:
              - Path=/catalog/**
  loadbalancer:
    configurations: health-check         # skip instances that are not ready
    health-check:
      interval: 5s
      path:
        default: /actuator/health/readiness   # this instance, not db/redis that every replica shares

Gateway, load balancer, ingress ou service mesh?

Esses quatro componentes se sobrepõem, e os produtos borram as fronteiras (o Envoy é o motor de muitos deles). O que os distingue é qual tráfego tratam e o que sabem:

Load balancer (lb) Ingress / Gateway API (ingress) API gateway (apigw) Service mesh (mesh)
Tráfego norte-sul (entrando no sistema) norte-sul entrando num cluster Kubernetes norte-sul, tráfego de API leste-oeste (serviço a serviço)
Papel principal distribuir a carga entre réplicas saudáveis rotear HTTP por host/path para Services políticas de API: auth, limites, keys, transformações mTLS, retries, timeouts, telemetria entre serviços
Conhece backends, saúde Services, hosts, paths consumidores, API keys, planos, rotas a identidade de cada serviço
Produtos típicos ALB/NLB, Nginx, HAProxy Envoy Gateway, Traefik, controllers de nuvem Kong, Apigee, AWS API Gateway, Azure APIM, Spring Cloud Gateway Istio, Linkerd, Cilium

Uma requisição típica do BoxOffice atravessa um load balancer (gerenciado, na entrada da região), o API gateway (políticas) e, entre os serviços, o mesh. No Kubernetes, o gateway costuma ser implantado como a implementação da Gateway API, fundindo os papéis de ingress e de gateway. O mesh é estudado em Rede & Segurança.


Backend for Frontend (BFF)

O app mobile e o app web precisam de coisas diferentes: o app mostra um card compacto do evento e funciona em redes lentas; a página web mostra o mapa completo de assentos e os detalhes do local. Uma API única e genérica ou traz dados demais para o mobile, ou obriga o app web a fazer muitas chamadas.

O padrão Backend for Frontend dá a cada tipo de cliente a sua própria camada fina de gateway, mantida pelo time que constrói aquele frontend2:

flowchart LR
    m([App mobile]) --> bm[BFF mobile]
    w([App web]) --> bw[BFF web]
    p([Parceiros]) --> pg[API gateway público]
    bm --> catalog[catalog]
    bm --> inventory[inventory]
    bw --> catalog
    bw --> inventory
    bw --> order[order]
    pg --> catalog

Cada BFF agrega e molda as respostas de que o seu cliente precisa (uma chamada para a "tela do evento" no mobile, com imagens menores e menos campos) e evolui no ritmo daquele frontend. Servidores GraphQL são usados com frequência como BFFs, porque o cliente descreve exatamente o formato de que precisa (veja Estilos de API).

Um gateway de uso geral Um BFF por cliente
Respostas genéricas; os clientes trazem dados demais ou fazem muitas chamadas sob medida para cada tela
Responsável time de plataforma; um gargalo compartilhado para mudanças o time de frontend é dono do seu BFF
Duplicação nenhuma alguma lógica comum duplicada entre BFFs
Número de componentes um um por tipo de cliente

Use um BFF quando os clientes forem bem diferentes e mantidos por times diferentes; deixe as políticas transversais (auth, rate limits) no gateway compartilhado na frente dos BFFs.


Rate limiting

O rate limiting limita quantas requisições um cliente pode fazer num período de tempo. Ele protege o backend de sobrecarga e abuso, impede que um cliente barulhento prejudique os outros (justiça) e faz valer os planos comerciais. Ele responde com 429 Too Many Requests — "você, especificamente, está mandando demais" —, diferente de 503 Service Unavailable, que significa "o servidor está sobrecarregado para todo mundo".

Todo rate limiter precisa decidir três coisas:

  1. Quem está sendo limitado — a chave: o id do usuário (do JWT), a API key, o IP do cliente, ou uma combinação como usuário + rota.
  2. Quanto — a política: 10 requisições por segundo com rajadas de 20; 1.000 por hora; 4 holds por evento por usuário.
  3. Como contar — o algoritmo, abaixo.

Token bucket

O algoritmo mais usado3. Cada chave tem um balde que guarda no máximo capacity tokens (o tamanho da rajada) e é reabastecido a uma taxa constante (o limite sustentado). Cada requisição consome um token; se o balde está vazio, a requisição é rejeitada.

flowchart LR
    req([Requisição do fã 7]) --> chk{"Há token no<br/>balde do fã 7?"}
    refill(["Reabastece: +10 tokens/s<br/>até a capacidade 20"]) -.-> chk
    chk -->|"sim: consome 1 token"| fwd[Encaminha para inventory]
    chk -->|"não: balde vazio"| rej["429 Too Many Requests<br/>Retry-After: 1"]

A implementação não precisa de timer: guarde a contagem de tokens e o instante da última atualização e, a cada requisição, some elapsed × rate tokens (limitado à capacidade) antes de verificar. Dois números por chave, trabalho O(1) por requisição.

A simulação abaixo mostra o balde de um fã (capacidade 20, reabastecimento de 10/s) sob um padrão de tráfego ilustrativo: navegação normal a 4 req/s, uma rajada de 2 segundos a 40 req/s (o fã aperta "atualizar" sem parar às 10:00) e depois 8 segundos a 14 req/s.

2026-11-01T21:55:02.454528 image/svg+xml Matplotlib v3.11.2, https://matplotlib.org/

Leia os dois painéis juntos:

  • Durante a rajada, o primeiro segundo aceita cerca de 29 requisições — os 20 tokens guardados mais o reabastecimento — e depois o limitador se acomoda na taxa de reabastecimento de 10/s; o excesso é rejeitado.
  • Durante a fase sustentada a 14 req/s, o balde se esvazia devagar (perde 4 tokens por segundo) e aceita tudo por cerca de cinco segundos; depois disso, aceita exatamente 10/s.
  • Quando o tráfego se acalma (4 req/s, portanto um reabastecimento líquido de 6 tokens/s), o balde volta a encher em cerca de três segundos, e a próxima rajada será absorvida de novo.

É o comportamento que você normalmente quer: rajadas curtas são toleradas, excesso sustentado não. Os dois parâmetros se traduzem diretamente em linguagem de produto — "10 requisições por segundo, rajadas de até 20".

Pense antes: um balde tem capacidade 50 e reabastece a 5 tokens/s. Um cliente que ficou ocioso por muito tempo manda 100 requisições de uma vez. Quantas são aceitas imediatamente? E quanto tempo até ele poder mandar outras 50 de uma vez?

50 são aceitas (o balde cheio); as outras 50 são rejeitadas. O balde fica vazio, então precisa de 10 s (50 tokens ÷ 5 tokens/s) para encher de novo. Num período longo, o cliente nunca consegue passar de uma média de 5 req/s, qualquer que seja o seu padrão.

Leaky bucket

As requisições entram numa fila de tamanho fixo e saem dela a uma taxa constante, como água vazando de um balde furado. Se a fila está cheia, as requisições novas são descartadas. A saída é perfeitamente suave — o backend vê exatamente a taxa de vazamento —, ao custo de latência adicional para as requisições enfileiradas e de nenhuma rajada chegar ao backend. O limit_req do Nginx é um leaky bucket; com burst=… nodelay, ele se comporta de modo muito parecido com um token bucket.

Contador de janela fixa

Conte as requisições por chave em janelas fixas — "minuto 10:00", "minuto 10:01" — com um contador que zera a cada fronteira: INCR ratelimit:fan7:202610171000 com expiração de 60 s. Simples e barato, mas tem um problema de fronteira: com um limite de 100 por minuto, um cliente pode mandar 100 às 10:00:59 e outras 100 às 10:01:00 — 200 requisições em dois segundos, o dobro da taxa pretendida, todas aceitas.

Log de janela deslizante

Guarde o timestamp de cada requisição (por exemplo, num sorted set do Redis); a cada requisição, descarte os timestamps mais antigos que a janela e conte o resto. Exato — não há problema de fronteira —, mas a memória cresce com o limite: um limite de 1.000 por hora guarda até 1.000 timestamps por chave.

Contador de janela deslizante

Uma aproximação barata que resolve o problema de fronteira: mantenha os contadores da janela fixa atual e da anterior e pondere a anterior por quanto dela ainda se sobrepõe à janela deslizante:

\[ \text{estimativa} = \text{atual} + \text{anterior} \times \left(1 - \frac{\text{tempo decorrido na janela atual}}{\text{tamanho da janela}}\right) \]

Exemplo: limite de 100/min. O minuto anterior teve 80 requisições; 15 s depois do início do minuto atual, houve 30. Estimativa = 30 + 80 × (1 − 15/60) = 30 + 60 = 90 → a requisição é permitida. Ele supõe que as requisições da janela anterior estavam distribuídas por igual, o que é próximo o bastante na prática; a Cloudflare relatou que essa aproximação errou numa fração ínfima das requisições em tráfego real4.

Comparando os algoritmos

Algoritmo Memória por chave Rajadas Precisão Uso típico
Token bucket 2 números permitidas, até a capacidade boa API gateways (AWS API Gateway, Spring Cloud Gateway, Envoy)
Leaky bucket uma fila suavizadas boa proteger um backend que precisa de taxa constante; limit_req do Nginx
Janela fixa 1 contador até 2× nas fronteiras fraca nas fronteiras cotas simples (por dia / por mês)
Log de janela deslizante 1 timestamp por requisição nenhuma além do limite exata limites baixos em que a exatidão importa (tentativas de login)
Contador de janela deslizante 2 contadores aproximadamente limitadas muito boa APIs de alto volume (Cloudflare)

Rate limiting distribuído

O BoxOffice roda seis réplicas do gateway. Se cada uma mantiver os contadores em memória, um fã cujas requisições se espalham entre elas recebe seis vezes o limite. Um rate limit que precisa valer entre réplicas exige um store de contadores compartilhado, quase sempre o Redis: rápido (menos de um milissegundo na mesma zona), com operações atômicas e expiração.

O read-modify-write de um token bucket precisa ser atômico — duas réplicas do gateway lendo "resta 1 token" ao mesmo tempo aceitariam as duas. Um script Lua roda atomicamente dentro do Redis:

token_bucket.lua
-- KEYS[1] = bucket key (e.g. "rl:user:7:holds")
-- ARGV[1] = capacity, ARGV[2] = refill rate (tokens/s), ARGV[3] = cost of this request
local capacity = tonumber(ARGV[1])
local rate     = tonumber(ARGV[2])
local cost     = tonumber(ARGV[3])

local t   = redis.call("TIME")                        -- Redis clock: no skew between gateways
local now = tonumber(t[1]) * 1000 + math.floor(tonumber(t[2]) / 1000)

local b      = redis.call("HMGET", KEYS[1], "tokens", "ts")
local tokens = tonumber(b[1]) or capacity
local ts     = tonumber(b[2]) or now

tokens = math.min(capacity, tokens + (now - ts) / 1000 * rate)   -- refill since last call
local allowed = 0
if tokens >= cost then
  tokens = tokens - cost
  allowed = 1
end

redis.call("HSET", KEYS[1], "tokens", tokens, "ts", now)
redis.call("PEXPIRE", KEYS[1], math.ceil(capacity / rate * 1000) * 2)  -- idle keys disappear
return { allowed, tostring(tokens) }

Pontos de projeto que vale a pena dizer em voz alta:

  • Uma ida e volta por requisição (de algumas centenas de microssegundos a um milissegundo) — aceitável no gateway, mas está no caminho crítico de toda requisição.
  • O tempo vem do Redis (TIME), não de cada gateway: os relógios dos gateways derivam (veja Tempo & relógios).
  • Fail open ou fail closed? Se o Redis estiver inacessível, o gateway deve aceitar tudo (protegendo a disponibilidade) ou rejeitar tudo (protegendo o backend)? A maioria das APIs faz fail open com um limite local, por réplica, como fallback.
  • Hot keys e escala: um Redis Cluster distribui as chaves em shards por hash; uma única chave muito quente (um limite global) cai num único shard. Chaves por usuário se espalham naturalmente.
  • Alternativas aproximadas: cada réplica aplica limit / replicas localmente, ou sincroniza os contadores de forma assíncrona a cada 100 ms. Menos exato, sem ida e volta extra.

Onde limitar

Camada Chave Protege contra Exemplo
Borda / CDN / WAF IP, fingerprint, ASN inundações, scrapers, credential stuffing — antes de qualquer custo 100 req/10 s por IP em /login
API gateway id do usuário, API key, rota usuários abusivos, cotas de plano, justiça 10 req/s por fã, rajadas de 20
Serviço entidade de negócio abuso de negócio que o gateway não enxerga no máximo 6 assentos por hold, 1 hold ativo por evento por usuário
Saída (lado do cliente) a cota do provedor estourar o limite de um terceiro (pagamento, e-mail) 100 req/s para o provedor de pagamento

Use várias camadas: grossa e barata na borda, ciente da identidade no gateway, regras de negócio nos serviços. O rate limiting também é um dos padrões de resiliência da aula 10 — lá, o ponto de vista é proteger um serviço de sobrecarga (load shedding).

Avisando o cliente

Um bom limitador diz aos clientes bem-comportados como se comportar:

429 response
HTTP/1.1 429 Too Many Requests
Retry-After: 2
RateLimit-Policy: "per-user";q=20;w=1
RateLimit: "per-user";r=0;t=2
Content-Type: application/problem+json

{"type": "https://boxoffice.example/problems/rate-limited", "title": "Too many requests", "status": 429}

Retry-After (em segundos, ou uma data) é HTTP padrão. Muitas APIs também enviam X-RateLimit-Limit, X-RateLimit-Remaining e X-RateLimit-Reset; o IETF está padronizando os campos RateLimit e RateLimit-Policy para o mesmo fim5. Os clientes devem respeitar o Retry-After e recuar com jitter — senão todo cliente rejeitado tenta de novo no mesmo instante.

Exemplos de configuração

O Spring Cloud Gateway traz um token bucket apoiado no Redis (RequestRateLimiter). O gateway do hands-on 4 não o liga — o BoxOffice limita na borda Nginx (quadro abaixo) —, mas é assim que você o adicionaria:

application.yaml
spring:
  cloud:
    gateway:
      server:
        webflux:      # the property prefix since Spring Cloud 2025 (as in hands-on 4)
          routes:
            - id: inventory-holds
              uri: http://inventory:8080
              predicates:
                - Path=/api/holds/**
              filters:
                - name: RequestRateLimiter
                  args:
                    redis-rate-limiter.replenishRate: 10   # tokens per second
                    redis-rate-limiter.burstCapacity: 20   # bucket capacity
                    redis-rate-limiter.requestedTokens: 1  # cost of one request
                    key-resolver: "#{@userKeyResolver}"
RateLimitConfig.java
@Configuration
public class RateLimitConfig {

    @Bean
    KeyResolver userKeyResolver() {   // the key: the authenticated user, or the IP
        return exchange -> exchange.getPrincipal()
            .map(Principal::getName)
            .switchIfEmpty(Mono.fromSupplier(() ->
                exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()));
    }
}

O Nginx limita por chave com um leaky bucket. Com o IP do cliente como chave, fica assim; o hands-on 3 usa a API key como chave (quadro abaixo):

nginx.conf
# 10 MB of shared memory holds about 160 000 IP states
limit_req_zone $binary_remote_addr zone=per_ip:10m rate=10r/s;

server {
    location /api/ {
        limit_req zone=per_ip burst=20 nodelay;   # absorb bursts of 20, no queueing delay
        limit_req_status 429;
        proxy_pass http://gateway;
    }
}
  • AWS API Gateway: usage plans com throttling (taxa + rajada, um token bucket) e cotas por API key; regras rate-based do AWS WAF por IP.
  • GCP: cotas e spike arrest do Apigee; regras de rate limiting do Cloud Armor.
  • Azure API Management: políticas rate-limit-by-key e quota-by-key.
  • Cloudflare: regras de rate limiting na borda (janela deslizante).

No código do BoxOffice: um rate limit na borda (lab 3)

A borda Nginx do Hands-on 3 dá a cada API key 20 req/s com rajada de 40 e responde 429 acima disso, sem tocar nas réplicas. O script k6 do lab manda um cliente guloso (50 req/s) e um educado (10 req/s) por 30 s: na execução do lab, o guloso recebeu 640 200s e 843 429s — mais ou menos os 20/s a que tem direito — e o educado 299 200s e nenhum 429. Note o comentário na configuração: uma requisição sem o header tem chave vazia e não é limitada, e a chave é escolhida pelo cliente. Em produção, a chave é uma identidade autenticada (o JWT do lab 4), mais um limite generoso por IP para o tráfego anônimo.

infra/nginx/nginx.conf (the limit)
# rate limiting per API key: 20 req/s per key, bursts of up to 40.
# Requests WITHOUT an X-Api-Key header have an empty key and are not limited
# (in production you would also limit them per client IP).
limit_req_zone  $http_x_api_key  zone=per_key:10m  rate=20r/s;
limit_req_status  429;
    location /catalog/ {
        limit_req  zone=per_key  burst=40  nodelay;

Experimente. Carregue o cenário rate-limiter e rode o perfil spike sem limites: o backend satura e o p99 explode para todo mundo. Ligue o rate limit do gateway e ajuste a taxa e a rajada: veja os 429 absorverem o excesso enquanto o p99 das requisições aceitas fica estável. O que acontece se o limite ficar acima da capacidade do backend?

Abrir o playground completo

O projeto completo de um rate limiter distribuído, como problema de entrevista, está no estudo de caso do rate limiter.


Trade-offs num relance

Decisão Opção A Opção B Escolha A quando…
Ponto de entrada um API gateway um BFF por cliente os clientes têm necessidades parecidas, ou um único time cuida de todos
Algoritmo token bucket contador de janela deslizante rajadas são aceitáveis e você quer parâmetros simples e claros
Store de contadores compartilhado (Redis) local por réplica o limite precisa ser preciso entre réplicas
Redis fora do ar fail open fail closed a disponibilidade importa mais que a proteção estrita
Produto de gateway gerenciado (AWS API GW, Apigee, APIM) auto-hospedado (Kong, Spring Cloud Gateway, Envoy) você quer planos, keys e portais sem operá-los
Lógica no gateway só políticas transversais regras de negócio sempre A

BoxOffice: o gateway no dia da venda

  • Roteamento: /api/events/** → catalog, /api/holds/** → inventory, /api/orders/** → order, /api/assistant/** → assistant.
  • Autenticação: o gateway valida o JWT emitido por auth (assinatura com as chaves públicas em cache, expiração, audience) e repassa X-User-Id; os GETs de catalog são públicos.
  • Rate limits (várias camadas):
    • borda/WAF: 100 requisições por 10 s por IP em /login; token da sala de espera obrigatório em /api/holds;
    • gateway: token bucket por usuário, 10 req/s com rajadas de 20 em /api/**, e 1 req/s com rajadas de 3 em POST /api/holds;
    • inventory: no máximo 6 assentos por hold e um hold ativo por usuário por evento (uma regra de negócio, garantida com o banco de dados).
  • Store: Redis (com uma réplica) para os baldes; se ele falhar, os gateways fazem fail open com um limite local de 1/6 do global.
  • Respostas: 429 com Retry-After; o app recua com jitter e mostra "muitas tentativas, tente de novo em alguns segundos".
  • Assistente: um limite separado e mais baixo e uma cota baseada no custo em tokens, porque uma chamada de LLM é muito mais cara que uma leitura do catálogo (veja Sistemas de IA).

Na entrevista

  • Coloque o API gateway no desenho de alto nível e liste o que ele centraliza: roteamento, autenticação, rate limiting, observabilidade. Depois siga em frente — a menos que a pergunta seja um rate limiter.
  • Se pedirem para projetar um rate limiter: esclareça chave, limite e escopo (por usuário? por IP? global?), escolha token bucket (rajadas) ou contador de janela deslizante (precisão), guarde os contadores no Redis com um script Lua atômico e discuta fail open vs closed, multi-região (limites locais por região ou sincronização assíncrona) e a resposta 429 + Retry-After.
  • Conheça o problema de fronteira da janela fixa (rajada de 2×) — é uma pergunta de follow-up clássica.
  • Mencione o BFF quando a pergunta tiver clientes muito diferentes (mobile, web, TV, parceiros).

Boas práticas

  • Responda 429 com Retry-After (e os headers RateLimit) — clientes bem-comportados recuam exatamente o tempo necessário, em vez de adivinhar.
  • Rode um limite novo em modo de contagem (só log) antes de aplicá-lo — meça quem teria sido limitado e só então ligue.
  • Dê a cada rota a sua própria política de timeout e retry, com retries só em métodos idempotentes — No BoxOffice: os timeouts de 1 s de conexão / 5 s de resposta do gateway e um filtro Retry limitado a GET no lab 4 (quadro acima).
  • Valide os JWTs localmente com chaves públicas em cache — um JWKS buscado e guardado em cache pelo gateway não custa nenhuma chamada a auth por requisição. No BoxOffice: jwk-set-uri no application.yaml do lab 4.
  • Meça o overhead e a capacidade do próprio gateway — p50 passando pelo gateway versus direto, e o seu ponto de ruptura, para dimensioná-lo como qualquer outra camada.

Armadilhas

  • Gateway como ponto único de falha ou gargalo: ele está no caminho de toda requisição. Rode várias réplicas em zonas diferentes, dimensione-o em testes de carga e mantenha pequeno o trabalho dele por requisição.
  • Lógica de negócio no gateway: scripts e plugins que calculam preços ou alocam assentos criam um segundo monólito, escondido, que ninguém testa. Mantenha o gateway burro e os serviços espertos.
  • Confiar só no gateway: os serviços ainda precisam verificar identidade e autorização (zero trust); um atacante dentro da rede contorna o gateway.
  • Contadores por réplica que multiplicam silenciosamente o limite pelo número de réplicas.
  • Rate limiting só por IP: milhares de fãs atrás do NAT de uma operadora móvel compartilham um IP; botnets têm milhares de IPs. Prefira chaves por usuário/API key, com limites por IP só na borda, e generosos.
  • Refazer um 429 imediatamente: clientes sem backoff transformam um limite numa tempestade de retries.

Como se conecta

flowchart LR
    GW["API gateway"] --> AUTH["validação de JWT<br/>→ Rede & Segurança"]
    GW --> RL["rate limiting"]
    RL --> TB["token bucket no Redis<br/>→ Cache (Redis)"]
    RL --> SHED["load shedding, 429<br/>→ Confiabilidade"]
    RL --> RETRY["retries do cliente com backoff<br/>→ tempestades de retries"]
    GW --> BFF["BFF por cliente"]
    BFF --> STYLES["REST, GraphQL, gRPC<br/>→ Computação: estilos de API"]
    GW --> OBS["access logs, métricas RED<br/>→ Observabilidade"]
    GW --> WR["sala de espera<br/>→ Mensageria (fila)"]

Componentes da paleta: apigw · auth (emite os tokens que o gateway valida) · cache (contadores de rate limit compartilhados) · queue (a sala de espera) · waf (limites grossos por IP, antes do gateway). Cenários: rate-limiter, retry-storm, flash-sale. Continua em: Estudo de caso: rate limiter, Confiabilidade — padrões, Rede & Segurança — identidade, Hands-on 4.

Pontos-chave

  • Um API gateway é o ponto de entrada único das APIs: roteamento, offload de autenticação, rate limits, transformações, agregação e observabilidade — só preocupações transversais.
  • O padrão BFF dá a cada tipo de cliente a sua própria camada sob medida.
  • O token bucket (capacidade = rajada, reabastecimento = taxa sustentada) é o algoritmo padrão de rate limiting; o leaky bucket suaviza a saída; a janela fixa permite rajadas de 2× nas fronteiras; o log de janela deslizante é exato, mas pesado em memória; o contador de janela deslizante é uma aproximação barata e precisa.
  • Entre réplicas, mantenha os contadores no Redis e atualize-os atomicamente (Lua); decida entre fail open ou closed.
  • Limite em camadas (borda, gateway, serviço), responda 429 com Retry-After e faça os clientes recuarem com jitter.


  1. RICHARDSON, C. Pattern: API Gateway / Backends for Frontends. microservices.io. ↩

  2. NEWMAN, S. Backends For Frontends, 2015. Veja também NEWMAN, S. Building Microservices, 2ª ed. O'Reilly, 2021. ↩

  3. XU, A. System Design Interview — An Insider's Guide, vol. 1, cap. 4 "Design a rate limiter". Byte Code LLC, 2020. ↩

  4. CLOUDFLARE. How we built rate limiting capable of scaling to millions of domains. Cloudflare Blog, 2017. ↩

  5. IETF HTTPAPI WG. RateLimit header fields for HTTP. Internet-Draft. ↩