Padrões de resiliência em chamadas HTTP síncronas

1. Introdução aos desafios de chamadas HTTP síncronas

Em arquiteturas modernas de microsserviços, chamadas HTTP síncronas são onipresentes. Cada requisição entre serviços representa um ponto de fragilidade que pode comprometer todo o sistema.

1.1. Dependências entre serviços e o efeito cascata de falhas

Quando um serviço A depende do serviço B, e B depende de C, uma falha em C pode derrubar toda a cadeia. Esse fenômeno é conhecido como "cascata de falhas" e pode transformar uma falha isolada em uma interrupção generalizada.

Exemplo de cascata:
Serviço A -> Serviço B -> Serviço C (falha)
Resultado: A, B e C ficam indisponíveis simultaneamente

1.2. Limitações de timeout, latência e disponibilidade em redes

Redes não são confiáveis. Timeouts mal configurados podem fazer com que uma thread espere 30 segundos por uma resposta que nunca virá. Em sistemas com alta concorrência, isso esgota rapidamente o pool de threads.

Configuração problemática:
Timeout global: 30 segundos
100 requisições simultâneas x 30s = 3000 segundos de bloqueio
Resultado: threads exauridas em poucos segundos

1.3. Objetivo da resiliência: falhas controladas vs. falhas catastróficas

O objetivo não é eliminar falhas, mas controlá-las. Uma falha controlada degrada um serviço específico; uma falha catastrófica derruba todo o sistema.

2. Timeout e retry: primeiras linhas de defesa

2.1. Configuração de timeouts por operação e por conexão

Timeouts devem ser definidos por operação, não globalmente. Uma operação de leitura de cache deve ter timeout de 100ms, enquanto uma consulta complexa pode tolerar 2 segundos.

Configuração de timeouts por operação:
GET /api/produtos -> timeout 500ms
POST /api/pedidos -> timeout 2000ms
GET /api/relatorios -> timeout 5000ms

2.2. Estratégias de retry: jitter, backoff exponencial e limite de tentativas

Retry sem estratégia é perigoso. O backoff exponencial com jitter evita tempestades de retry.

Exemplo de backoff exponencial com jitter:
Tentativa 1: espera 100ms
Tentativa 2: espera 200ms + random(0, 50ms)
Tentativa 3: espera 400ms + random(0, 100ms)
Tentativa 4: espera 800ms + random(0, 200ms)
Limite máximo: 3 tentativas

2.3. Idempotência e segurança em retries de requisições

Requisições POST que criam recursos precisam de idempotência. Um cabeçalho Idempotency-Key garante que o servidor ignore requisições duplicadas.

Requisição com idempotência:
POST /api/pagamentos
Idempotency-Key: uuid-12345
Corpo: { valor: 100 }
Se o retry ocorrer, o servidor retorna a resposta original

3. Circuit Breaker: proteção contra sobrecarga

3.1. Estados do circuito (fechado, aberto, semi-aberto) e transições

O circuit breaker possui três estados: fechado (requisições passam), aberto (requisições falham imediatamente) e semi-aberto (testa se o serviço recuperou).

Transições do circuit breaker:
Fechado -> taxa de erro > 50% -> Aberto
Aberto -> após 30 segundos -> Semi-aberto
Semi-aberto -> sucesso na requisição de teste -> Fechado
Semi-aberto -> falha na requisição de teste -> Aberto

3.2. Métricas para detecção de falhas: taxa de erro, latência, janela deslizante

A janela deslizante de 60 segundos com taxa de erro acima de 50% é uma configuração comum. A latência também pode ser métrica: se o p95 ultrapassar 2 segundos, abra o circuito.

Configuração de métricas:
Janela deslizante: 60 segundos
Limite de falhas: 10 falhas consecutivas
Taxa de erro: > 50%
Latência p95: > 2000ms

3.3. Fallback e degradação graciosa quando o circuito está aberto

Quando o circuito abre, o sistema deve oferecer uma resposta alternativa, mesmo que degradada.

Fallback quando circuito aberto:
Resposta normal: { status: "ok", dados: [...] }
Resposta com circuito aberto: { status: "degradado", dados: [], mensagem: "Serviço temporariamente indisponível" }

4. Bulkhead e isolamento de recursos

4.1. Separação de pools de conexão e threads por cliente ou endpoint

Cada cliente HTTP deve ter seu próprio pool de threads. Um serviço lento não deve consumir threads de outros serviços.

Pools separados por serviço:
Serviço A: pool de 10 threads, timeout 500ms
Serviço B: pool de 5 threads, timeout 2000ms
Serviço C: pool de 3 threads, timeout 10000ms
Total: 18 threads, isolamento garantido

4.2. Prevenção do efeito "noisy neighbor" entre chamadas síncronas

Um cliente que faz muitas chamadas não deve prejudicar outros clientes. O bulkhead garante que cada cliente tenha recursos dedicados.

Sem bulkhead: Cliente X usa 50 threads, Cliente Y fica sem threads
Com bulkhead: Cliente X tem limite de 20 threads, Cliente Y tem limite de 10 threads

4.3. Limitação de concorrência máxima por recurso externo

Defina um limite máximo de requisições simultâneas por endpoint externo.

Limitação de concorrência:
API de pagamentos: máximo 5 requisições simultâneas
API de estoque: máximo 10 requisições simultâneas
API de frete: máximo 3 requisições simultâneas

5. Cache e fallback com dados obsoletos

5.1. Cache local para respostas de serviços críticos com TTL configurável

Cache em memória para respostas frequentes reduz a dependência de serviços externos.

Configuração de cache:
Chave: GET /api/produtos/123
Valor: { nome: "Produto A", preco: 100 }
TTL: 60 segundos
Tamanho máximo: 1000 entradas

5.2. Estratégia de stale-while-revalidate para manter disponibilidade

Quando o cache expira, sirva o dado obsoleto enquanto revalida em segundo plano.

Stale-while-revalidate:
Cache expirado há 30 segundos
Serve dado obsoleto: { nome: "Produto A", preco: 100 }
Em segundo plano: requisição para atualizar cache
Se falhar, mantém dado obsoleto por mais 60 segundos

5.3. Fallback estático ou default quando o serviço está indisponível

Tenha valores padrão para dados críticos quando o serviço falha completamente.

Fallback estático para preço:
Tenta API de preços -> falha
Retorna preço padrão: { preco: 0, mensagem: "Preço temporariamente indisponível" }

6. Monitoramento e observabilidade de falhas

6.1. Métricas essenciais: taxa de sucesso, latência percentil, contagem de circuit breakers

Monitore as métricas que indicam a saúde dos padrões de resiliência.

Métricas a monitorar:
taxa_sucesso_http: 98.5%
latencia_p95_ms: 450
contagem_circuit_breaker_aberto: 3
contagem_retry: 150
taxa_fallback: 1.2%

6.2. Logs estruturados e tracing distribuído para diagnóstico de gargalos

Cada falha deve ser registrada com contexto suficiente para diagnóstico.

Log estruturado de falha:
{
  "evento": "circuit_breaker_aberto",
  "servico": "api-pagamentos",
  "metodo": "POST",
  "url": "/api/pagamentos",
  "duracao_ms": 2500,
  "trace_id": "abc-123-def"
}

6.3. Alertas proativos com base em limites de resiliência

Alertas devem ser acionados antes que a falha se torne catastrófica.

Alertas configurados:
Alerta 1: taxa_erro > 10% por 5 minutos -> notificar equipe
Alerta 2: circuit_breaker_aberto > 5 -> notificar equipe
Alerta 3: latencia_p95 > 2000ms -> notificar equipe

7. Considerações finais e boas práticas

7.1. Combinação de padrões: retry + circuit breaker + bulkhead

Os padrões funcionam melhor juntos. Retry para falhas transitórias, circuit breaker para falhas persistentes e bulkhead para isolar recursos.

Combinação de padrões:
1. Bulkhead: pool de 10 threads para serviço X
2. Timeout: 500ms por requisição
3. Retry: 3 tentativas com backoff exponencial
4. Circuit breaker: abre após 5 falhas consecutivas
5. Fallback: cache local ou resposta degradada

7.2. Testes de caos e simulação de falhas em ambientes de staging

Teste seus padrões de resiliência simulando falhas reais.

Testes de caos:
Teste 1: Simular timeout de 10 segundos no serviço B
Teste 2: Simular erro 500 em 50% das requisições
Teste 3: Simular latência de 5 segundos em 30% das requisições
Verificar: circuit breaker abre, fallback funciona, sistema não cai

7.3. Evolução contínua: ajuste dinâmico de parâmetros baseado em métricas

Parâmetros estáticos não funcionam para sistemas dinâmicos. Ajuste timeouts, limites de retry e thresholds do circuit breaker com base em métricas em tempo real.

Ajuste dinâmico de parâmetros:
Se latencia_p95 > 1000ms por 10 minutos:
  Reduzir timeout de 2000ms para 1500ms
  Reduzir limite de retry de 3 para 2
  Reduzir threshold do circuit breaker de 50% para 40%

Referências