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.
- A API de Checkout aceita novas requisições de clientes enquanto aguarda o Frete.
- O pool de threads e os sockets HTTP do Checkout se esgotam rapidamente.
- O Checkout para de responder a outros endpoints saudáveis e seus health checks começam a falhar.
- 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
- 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).
- 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.
- 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.