Circuit Breaker
Como proteger seus serviços de falhas em cascata usando o padrão Circuit Breaker com estados, fallbacks e recuperação automática.
Que problema resolve
Em uma arquitetura de microsserviços, os serviços dependem uns dos outros. Quando um serviço downstream fica lento ou para de responder, os serviços que o chamam acumulam conexões abertas, esgotam seus thread pools e, eventualmente, também falham. Esse efeito dominó é conhecido como falha em cascata.
Exemplo do problema
Usuário → API Gateway → Serviço A → Serviço B (lento/fora do ar)
⏳ timeout 30s
⏳ timeout 30s
⏳ timeout 30s
↓
Serviço A esgota suas threads
esperando resposta de B
↓
API Gateway esgota suas threads
esperando resposta de A
↓
Todo o sistema para de responder
Sem proteção, um único serviço lento pode derrubar toda a plataforma. O Circuit Breaker atua como um disjuntor automático que corta a comunicação com um serviço que está falhando, evitando que o problema se propague.
O padrão foi popularizado por Michael Nygard em seu livro Release It! e recebe esse nome dos disjuntores elétricos que cortam o circuito ao detectar uma sobrecarga.
Impacto real das falhas em cascata
| Métrica | Sem Circuit Breaker | Com Circuit Breaker |
|---|---|---|
| Tempo de detecção | Minutos (quando os usuários reportam) | Segundos (mudança de estado do circuito) |
| Serviços afetados | Todos os que dependem do serviço fora do ar | Apenas o serviço fora do ar |
| Recuperação | Manual (reiniciar serviços, limpar filas) | Automática (semiaberto detecta a recuperação) |
| Experiência do usuário | Timeouts longos, erros 500 | Resposta rápida com fallback ou erro claro |
| Recursos consumidos | Threads e conexões esgotados | Recursos liberados imediatamente |
Como funciona
O Circuit Breaker é um proxy que se posiciona entre um serviço e suas dependências. Ele monitora as chamadas e, com base na taxa de falhas, decide se permite ou bloqueia as solicitações.
Os três estados
Falhas > limite
┌──────────┐ ──────────────────► ┌──────────┐
│ FECHADO │ │ ABERTO │
│ (normal) │ ◄──────────────── │ (cortado)│
└──────────┘ Teste bem-sucedido └────┬─────┘
▲ │
│ Timeout expira │
│ ▼
│ ┌───────────┐
└────── Teste bem-sucedido ─│ SEMIABERTO │
└───────────┘
Teste falha ─────► ABERTO
Estado Fechado (Closed)
É o estado normal. Todas as solicitações passam para o serviço downstream. O circuit breaker monitora os resultados:
- Conta as falhas recentes (erros, timeouts)
- Se a taxa de falhas ultrapassa um limite (ex.: 50% de falhas nas últimas 10 chamadas), muda para Aberto
Estado Aberto (Open)
O circuito está cortado. As solicitações não chegam ao serviço downstream. Em vez disso, o circuit breaker responde imediatamente com um erro ou executa um fallback.
- Evita enviar tráfego para um serviço que sabemos estar falhando
- Libera recursos (threads, conexões) que seriam desperdiçados esperando timeouts
- Após um período de espera (ex.: 30 segundos), muda para Semiaberto
Estado Semiaberto (Half-Open)
Estado de teste. O circuit breaker permite a passagem de um número limitado de solicitações para verificar se o serviço se recuperou:
- Se as solicitações de teste forem bem-sucedidas → volta para Fechado
- Se as solicitações de teste falharem → volta para Aberto
Parâmetros de configuração
| Parâmetro | Descrição | Exemplo | Consideração |
|---|---|---|---|
| Failure threshold | Percentual de falhas para abrir | 50% | Muito baixo = falsos positivos, muito alto = reação lenta |
| Request volume | Mínimo de solicitações antes de avaliar | 10 solicitações | Evita abrir o circuito por 1 falha isolada |
| Sleep window | Tempo no estado aberto | 30 segundos | Deve dar tempo ao serviço para se recuperar |
| Success threshold | Sucessos para fechar o circuito | 3 consecutivas | Mais alto = mais confiança na recuperação |
| Timeout | Tempo máximo de espera | 5 segundos | Deve ser menor que o timeout do cliente |
Estratégias de fallback
Quando o circuito está aberto, em vez de retornar um erro cru, você pode implementar fallbacks:
| Estratégia | Descrição | Exemplo |
|---|---|---|
| Valor padrão | Retornar dados estáticos | Exibir catálogo em cache |
| Cache | Última resposta bem-sucedida em cache | Preço do produto de 5 min atrás |
| Serviço alternativo | Redirecionar para um backup | Usar serviço de pagamentos secundário |
| Degradação graciosa | Funcionalidade reduzida | Exibir “preço indisponível” |
| Fail fast | Erro imediato com mensagem clara | HTTP 503 com header retry-after |
Exemplo de fluxo completo
Estado: FECHADO
Chamada 1 → Serviço B → ✅ OK
Chamada 2 → Serviço B → ✅ OK
Chamada 3 → Serviço B → ❌ Timeout
Chamada 4 → Serviço B → ❌ Erro 500
Chamada 5 → Serviço B → ❌ Timeout
→ Taxa de falhas: 60% > limite 50%
→ Muda para ABERTO ⚡
Estado: ABERTO
Chamada 6 → ⚡ Fallback imediato (não chega a B)
Chamada 7 → ⚡ Fallback imediato
... (30 segundos de espera)
→ Muda para SEMIABERTO
Estado: SEMIABERTO
Chamada 8 → Serviço B → ✅ OK (teste)
Chamada 9 → Serviço B → ✅ OK (teste)
Chamada 10 → Serviço B → ✅ OK (teste)
→ 3 sucessos consecutivos
→ Muda para FECHADO ✅
Exemplo prático: Circuit Breaker em um serviço de pagamentos
Imagine um serviço de pedidos que chama um provedor externo de pagamentos:
// Pseudocódigo de configuração do Circuit Breaker
circuitBreaker = new CircuitBreaker({
name: "payment-provider",
failureThreshold: 50, // 50% de falhas
requestVolume: 10, // mínimo 10 requests para avaliar
sleepWindow: 30000, // 30 segundos no estado aberto
successThreshold: 3, // 3 sucessos para fechar
timeout: 5000, // 5 segundos de timeout por chamada
fallback: () => ({
status: "PENDING",
message: "Pagamento na fila, será processado quando o serviço se recuperar"
})
});
// Uso
async function procesarPago(pedidoId, monto) {
try {
resultado = await circuitBreaker.execute(() =>
paymentProvider.charge(pedidoId, monto)
);
return resultado;
} catch (error) {
if (error instanceof CircuitOpenError) {
// O circuito está aberto, o fallback foi executado
encolarPagoParaReintento(pedidoId, monto);
return error.fallbackResult;
}
throw error;
}
}
Monitoramento do Circuit Breaker
As mudanças de estado do circuito são sinais valiosos para a equipe de operações:
// Métricas a expor
circuit_breaker_state{name="payment-provider"} = "closed" | "open" | "half_open"
circuit_breaker_calls_total{name="payment-provider", result="success"}
circuit_breaker_calls_total{name="payment-provider", result="failure"}
circuit_breaker_calls_total{name="payment-provider", result="rejected"}
circuit_breaker_state_transitions_total{name="payment-provider", from="closed", to="open"}
// Alertas recomendados
ALERT CircuitBreakerOpen
IF circuit_breaker_state == "open"
FOR 1m
LABELS { severity = "warning" }
ANNOTATIONS { summary = "Circuit breaker aberto para payment-provider" }
Vantagens
- Previne falhas em cascata: Um serviço fora do ar não arrasta o resto do sistema
- Fail fast: As solicitações falham imediatamente em vez de esperar timeouts longos
- Liberação de recursos: Não se desperdiçam threads nem conexões em chamadas que vão falhar
- Recuperação automática: O estado semiaberto detecta quando o serviço se recupera
- Experiência do usuário melhorada: Os fallbacks oferecem respostas degradadas em vez de erros
- Observabilidade: As mudanças de estado são sinais claros para alertas e monitoramento
Trade-offs / Desvantagens
- Configuração delicada: Limites incorretos podem abrir o circuito cedo ou tarde demais
- Complexidade nos fallbacks: Projetar fallbacks úteis para cada dependência exige esforço
- Dados desatualizados: Os fallbacks baseados em cache podem retornar informação obsoleta
- Testes complexos: Simular os três estados e as transições exige infraestrutura
- Latência na recuperação: A sleep window introduz um atraso entre a recuperação real e a detecção
- Não resolve a causa raiz: O circuit breaker protege o sistema, mas o serviço downstream ainda precisa ser corrigido
Quando usar
- Chamadas a serviços externos ou microsserviços que podem falhar ou ficar lentos
- Sistemas em que uma falha em uma dependência não deve afetar a disponibilidade geral
- Quando você precisa proteger recursos compartilhados (thread pools, connection pools)
- APIs que chamam serviços de terceiros com SLAs variáveis
- Sistemas com requisitos de alta disponibilidade em que a degradação graciosa é preferível
- Quando você já tem métricas e alertas para monitorar o estado dos circuitos
Quando evitar
- Chamadas a recursos locais (banco de dados local, arquivos) em que o overhead não se justifica
- Operações que precisam ser concluídas a qualquer custo (sem alternativa de fallback possível)
- Sistemas simples com poucas dependências externas
- Quando a latência adicional do proxy é inaceitável
- Chamadas assíncronas em que você já tem filas com retentativas e dead letter queues
- Protótipos ou MVPs em que a resiliência não é prioridade
Tecnologias e implementações comuns
| Categoria | Opções |
|---|---|
| Bibliotecas | Resilience4j (Java), Polly (.NET), Hystrix (Java, legado), opossum (Node.js) |
| Service Mesh | Istio, Linkerd, Envoy (circuit breaking a nível de infraestrutura) |
| Cloud | AWS App Mesh, Azure Traffic Manager |
| Monitoramento | Prometheus + Grafana, Datadog, New Relic |
| Padrões complementares | Retry com backoff, Bulkhead, Timeout, Rate Limiting |
Relação com outros padrões
O Circuit Breaker raramente é usado sozinho. Ele funciona melhor em combinação com outros padrões de resiliência:
┌─────────────────────────────────────────┐
│ Cadeia de resiliência │
│ │
│ Solicitação │
│ │ │
│ ▼ │
│ ┌──────────┐ │
│ │ Timeout │ Limita o tempo de espera │
│ └────┬─────┘ │
│ ▼ │
│ ┌──────────┐ │
│ │ Retry │ Retenta com backoff │
│ └────┬─────┘ │
│ ▼ │
│ ┌──────────┐ │
│ │ Circuit │ Corta se houver muitas │
│ │ Breaker │ falhas consecutivas │
│ └────┬─────┘ │
│ ▼ │
│ ┌──────────┐ │
│ │ Bulkhead │ Isola recursos por │
│ │ │ dependência │
│ └────┬─────┘ │
│ ▼ │
│ Serviço downstream │
└─────────────────────────────────────────┘
- Timeout Pattern: Define quanto esperar antes de considerar uma chamada como falha. O timeout alimenta o circuit breaker com dados de falhas
- Retry Pattern: Retenta chamadas falhas com backoff exponencial. As retentativas ocorrem antes de o circuito abrir; uma vez aberto, não se retenta
- Bulkhead: Isola os recursos (threads, conexões) por dependência para que um serviço lento não consuma todos os recursos do sistema
- Saga Pattern: Quando um passo da saga falha e o circuit breaker está aberto, a saga aciona compensações em vez de retentar indefinidamente
- API Gateway: O gateway pode implementar circuit breakers para cada serviço backend, centralizando a proteção
- Idempotency Pattern: Quando o circuito fecha e as operações são retentadas, a idempotência garante que os efeitos não sejam duplicados
Erros comuns
| Erro | Consequência | Solução |
|---|---|---|
| Limite baixo demais | Circuito abre por falhas isoladas | Aumentar o request volume mínimo |
| Sleep window curta demais | Circuito oscila entre aberto e fechado | Dar mais tempo de recuperação |
| Sem fallback definido | Erro cru para o usuário quando o circuito abre | Projetar fallbacks para cada dependência |
| Um circuit breaker global | Um serviço lento afeta todos | Um circuit breaker por dependência |
| Não monitorar as transições | Você não detecta problemas a tempo | Alertas por mudanças de estado |
| Ignorar o estado semiaberto | Tráfego excessivo no teste | Limitar as requests de teste |
Próximos passos
O Circuit Breaker é uma peça fundamental da resiliência em microsserviços. Para completar sua compreensão dos padrões de resiliência, explore o Retry Pattern para lidar com falhas transitórias com backoff, o Timeout Pattern para limitar esperas e o Bulkhead Pattern para isolar recursos por dependência.