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étricaSem Circuit BreakerCom Circuit Breaker
Tempo de detecçãoMinutos (quando os usuários reportam)Segundos (mudança de estado do circuito)
Serviços afetadosTodos os que dependem do serviço fora do arApenas o serviço fora do ar
RecuperaçãoManual (reiniciar serviços, limpar filas)Automática (semiaberto detecta a recuperação)
Experiência do usuárioTimeouts longos, erros 500Resposta rápida com fallback ou erro claro
Recursos consumidosThreads e conexões esgotadosRecursos 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âmetroDescriçãoExemploConsideração
Failure thresholdPercentual de falhas para abrir50%Muito baixo = falsos positivos, muito alto = reação lenta
Request volumeMínimo de solicitações antes de avaliar10 solicitaçõesEvita abrir o circuito por 1 falha isolada
Sleep windowTempo no estado aberto30 segundosDeve dar tempo ao serviço para se recuperar
Success thresholdSucessos para fechar o circuito3 consecutivasMais alto = mais confiança na recuperação
TimeoutTempo máximo de espera5 segundosDeve 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égiaDescriçãoExemplo
Valor padrãoRetornar dados estáticosExibir catálogo em cache
CacheÚltima resposta bem-sucedida em cachePreço do produto de 5 min atrás
Serviço alternativoRedirecionar para um backupUsar serviço de pagamentos secundário
Degradação graciosaFuncionalidade reduzidaExibir “preço indisponível”
Fail fastErro imediato com mensagem claraHTTP 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

CategoriaOpções
BibliotecasResilience4j (Java), Polly (.NET), Hystrix (Java, legado), opossum (Node.js)
Service MeshIstio, Linkerd, Envoy (circuit breaking a nível de infraestrutura)
CloudAWS App Mesh, Azure Traffic Manager
MonitoramentoPrometheus + Grafana, Datadog, New Relic
Padrões complementaresRetry 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

ErroConsequênciaSolução
Limite baixo demaisCircuito abre por falhas isoladasAumentar o request volume mínimo
Sleep window curta demaisCircuito oscila entre aberto e fechadoDar mais tempo de recuperação
Sem fallback definidoErro cru para o usuário quando o circuito abreProjetar fallbacks para cada dependência
Um circuit breaker globalUm serviço lento afeta todosUm circuit breaker por dependência
Não monitorar as transiçõesVocê não detecta problemas a tempoAlertas por mudanças de estado
Ignorar o estado semiabertoTráfego excessivo no testeLimitar 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.