Circuit Breaker na Prática: Como Impedir Falhas em Cascata com Polly no .NET

Circuitos eletrônicos integrados e proteção de sistemas

Por que retentativas cegas derrubam dependências downstream e como os estados Closed, Open e Half-Open protegem sua arquitetura distribuída contra colapsos em cadeia.

No desenvolvimento de sistemas distribuídos e microsserviços, a intuição comum de muitos desenvolvedores diante de uma falha de rede temporária é configurar um loop de retries imediatos. Afinal, se a chamada HTTP falhou por oscilação momentânea, tentar de novo parece a atitude mais óbvia.

No entanto, em ambientes corporativos de alta escala, o retry cego e desgovernado é uma das causas mais frequentes de apagões catastróficos. Quando um serviço downstream (como um gateway de pagamentos ou consulta de estoque) fica sobrecarregado e sua latência sobe, milhares de clientes reenviando requisições ao mesmo tempo criam uma verdadeira tempestade de retries (Retry Storm). O serviço que estava apenas lento é definitivamente nocauteado.

Para interromper essa propagação destrutiva e blindar o ecossistema, o padrão arquitetural obrigatório é o Circuit Breaker (Disjuntor de Circuito).


O Problema: O Efeito Dominó e a Exaustão de Recursos

Diferente de um erro 404 ou 400 (que respondem imediatamente), o tipo mais perigoso de falha em microsserviços é a latência assintomática. Quando um serviço remoto demora 15 segundos para responder ou sofre timeouts sucessivos, as threads e conexões HTTP do serviço chamador ficam presas aguardando a resposta.

O Cenário do Desastre:

Imagine o seguinte fluxo: a API de Checkout chama o serviço de Cálculo de Frete. O banco de dados do frete sofre um lock temporário e o serviço passa a responder em 20 segundos em vez de 50 milissegundos.

  1. A API de Checkout aceita novas requisições de clientes enquanto aguarda o Frete.
  2. O pool de threads e os sockets HTTP do Checkout se esgotam rapidamente.
  3. O Checkout para de responder a outros endpoints saudáveis e seus health checks começam a falhar.
  4. O Ingress Controller desativa as instâncias de Checkout e o tráfego restante sobrecarrega os pods remanescentes.

Um problema pontual no cálculo de frete acabou de derrubar toda a camada de checkout do e-commerce. A falha se propagou em cascata.


A Solução: A Máquina de Estados do Circuit Breaker

Inspirado nos disjuntores da rede elétrica residencial (que desarmam para evitar curto-circuitos e incêndios), o Circuit Breaker atua como um invólucro (proxy) sobre as chamadas remotas, operando sob uma máquina de três estados:

1. Closed (Fechado - Operação Normal)

Todas as requisições passam diretamente para o serviço remoto. O Circuit Breaker monitora a taxa de falhas e a latência em uma janela de tempo deslizante. Enquanto a operação estiver saudável, o circuito permanece fechado.

2. Open (Aberto - Falha Rápida)

Se a taxa de falhas ultrapassar o limiar tolerado (ex: 50% de erros em 30 segundos), o circuito desarma. A partir desse instante, qualquer nova requisição falha imediatamente (Fail-Fast) na própria aplicação chamadora, sem sequer tentar abrir conexão de rede. Isso produz dois benefícios imediatos:

  • Libera as threads do serviço chamador em frações de milissegundo.
  • Interrompe o bombardeio de requisições sobre o serviço downstream, dando tempo e fôlego para que ele se recupere.

3. Half-Open (Meio-Aberto - Sonda de Teste)

Após um período de espera configurado (Break Duration, ex: 30 segundos), o circuito entra no estado de transição. Ele permite que um número restrito de requisições de teste (probes) passe para o serviço downstream. Se essas requisições forem bem-sucedidas, o circuito é rearmado (volta para Closed). Se falharem, o circuito reabre imediatamente por mais um ciclo de descanso.


Onde isso vive na arquitetura? Implementando com Polly v8 no .NET

No ecossistema .NET moderno, a biblioteca padrão da indústria é o Polly, que a partir da versão 8 introduziu o conceito de Resilience Pipelines de altíssima performance e alocação mínima de memória.

Veja como configurar o Circuit Breaker no Program.cs acoplado ao HttpClient:

using Polly;
using Polly.CircuitBreaker;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpClient("FreteClient", client =>
{
    client.BaseAddress = new Uri("https://api.frete.empresa.com/");
    client.Timeout = TimeSpan.FromSeconds(5);
})
.AddResilienceHandler("circuito-frete", pipeline =>
{
    // 1. Timeout por tentativa individual
    pipeline.AddTimeout(TimeSpan.FromSeconds(3));

    // 2. Circuit Breaker defensivo
    pipeline.AddCircuitBreaker(new HttpCircuitBreakerStrategyOptions
    {
        // Percentual de falhas para abrir o circuito (ex: 50%)
        FailureRatio = 0.5,
        
        // Janela de análise estatística
        SamplingDuration = TimeSpan.FromSeconds(15),
        
        // Número mínimo de requisições antes de avaliar (vital para evitar falso-positivo)
        MinimumThroughput = 10,
        
        // Tempo em que o circuito fica aberto antes do Half-Open
        BreakDuration = TimeSpan.FromSeconds(30),
        
        OnOpened = args =>
        {
            Console.WriteLine($"[ALERTA SRE] Circuito Frete ABERTO! Duração: {args.BreakDuration}");
            return ValueTask.CompletedTask;
        },
        OnClosed = args =>
        {
            Console.WriteLine("[INFO SRE] Circuito Frete normalizado e FECHADO.");
            return ValueTask.CompletedTask;
        }
    });
});

var app = builder.Build();
app.Run();

As Três Regras de Ouro do Circuit Breaker em Produção

  1. Sempre configure MinimumThroughput: Se você configurar apenas 50% de falhas sem definir volume mínimo, em um horário de baixo tráfego (madrugada) duas requisições isoladas que falharem abrirão o circuito desnecessariamente. Sempre exija uma amostra mínima (ex: pelo menos 10 ou 20 requisições).
  2. Projete Fallbacks Semânticos: Quando o circuito abrir, o que o seu serviço entrega ao usuário? Em vez de cuspir um erro 500 feio, use um fallback elegante: retorne uma estimativa média de frete armazenada em cache, entregue dados em modo degradado ou exiba uma mensagem clara orientando o cliente.
  3. Circuit Breaker não substitui Timeout: O disjuntor mede a taxa de falhas. Se a sua chamada não tiver um timeout estrito, uma requisição pode ficar travada por 60 segundos antes de contar como erro, matando a sua memória antes de o circuito ter a chance de desarmar.

Conclusão

Projetar microsserviços não é acreditar na ilusão de que a rede nunca oscilará ou que dependências externas nunca cairão. Resiliência real é construir sistemas com a disciplina de falhar rápido, proteger a capacidade operacional das aplicações vizinhas e se reestabelecer de forma autônoma assim que a tempestade passar.

Ao incorporar o Circuit Breaker e fallbacks bem desenhados no seu ferramental, você eleva a maturidade da sua arquitetura, preserva a saúde dos seus clusters e evita que pequenas falhas locais se transformem em incidentes globais.