Marcio Cunha

Design de Sistemas Tolerantes a Falhas com Bulkheads e Circuit Breakers

Aprenda a projetar sistemas resilientes combinando o isolamento de recursos por bulkheads e a proteção contra cascatas de erros com circuit breakers dinâmicos.

Marcio Cunha•4 min
Também disponível em:EnglishEspañol
Resumo
  • Compartimentos estanques impedem que o colapso de um único componente contamine o restante da arquitetura de microsserviços.
  • Interromper chamadas repetidamente para serviços instáveis poupa recursos computacionais e acelera o tempo de recuperação.
  • Métricas em tempo real permitem calibrar limites operacionais de forma dinâmica conforme o tráfego varia ao longo do dia.
  • Degradação graciosa garante que funções secundárias sejam desativadas temporariamente para preservar o núcleo transacional.
  • Testes de caos validam a eficácia das barreiras de isolamento sob condições extremas de falhas de rede.

O Desafio da Resiliência em Sistemas Distribuídos Modernos

Quando construímos aplicações modernas baseadas em microsserviços, assumimos o risco inerente de que componentes falharão a qualquer momento. Na prática, isso significa que um banco de dados sobrecarregado não deve derrubar o catálogo de produtos inteiro ou impedir que usuários façam login. A resiliência arquitetural exige que o sistema aceite a falha como um evento normal e contenha seus danos antes que ocorra um efeito dominó catastrófico.

Em arquiteturas monolíticas antigas, o escopo da falha costumava ficar restrito a um processo reiniciando. Hoje, com dezenas de serviços conversando via rede, a lentidão em um endpoint de pagamento pode esgotar as conexões de toda a aplicação em segundos. Para combater esse comportamento indesejado, engenheiros recorrem a padrões de projeto específicos que isolam responsabilidades e controlam o fluxo de tráfego degradado.

Neste artigo, vamos explorar como estruturar defesas robustas combinando o isolamento físico de recursos e a interrupção inteligente de requisições. Veremos como esses mecanismos operam sob o capô, quais trade-offs arquiteturais impactam o dia a dia do desenvolvedor e como implementar essas salvaguardas em ambientes de alta criticidade sem sacrificar a manutenibilidade do código.

Isolamento de Recursos com o Padrão Bulkhead

O termo bulkhead vem da engenharia naval, especificamente dos compartimentos estanques nos cascos dos navios que impedem que a embarcação afunde caso uma seção sofra uma perfuração. Na computação, aplicar o padrão bulkhead significa particionar recursos computacionais — como threads, conexões de banco de dados ou memória — em silos isolados para que o esgotamento em uma área não afete as demais.

Imagine um sistema de e-commerce que utiliza o mesmo pool de conexões HTTP para consultar o estoque e para processar recomendações de produtos. Se o serviço de recomendações sofrer uma latência extrema, ele consumirá todas as conexões disponíveis, deixando o checkout totalmente indisponível. Ao aplicar bulkheads, separamos pools distintos para cada dependência, garantindo que o núcleo transacional continue operando de forma isolada.

Na prática, configurar esses compartimentos exige monitorar o consumo máximo de cada dependência e estabelecer limites rígidos de alocação. Se o pool dedicado ao serviço de frete atingir cem por cento de ocupação, novas requisições para ele falham imediatamente com um erro controlado, enquanto as rotas de pagamento seguem intactas, utilizando seus próprios recursos reservados.

Proteção Dinâmica com Circuit Breakers

Enquanto o bulkhead isola o estrago, o circuit breaker atua como um disjuntor elétrico inteligente que interrompe o fluxo de tráfego para um serviço externo que apresenta instabilidade crônica. Ele monitora continuamente as chamadas e, quando a taxa de falhas ultrapassa um limite aceitável, desarma o circuito, fazendo com que requisições subsequentes falhem instantaneamente sem sobrecarregar o sistema de destino.

Um circuit breaker opera normalmente em três estados distintos: fechado, aberto e meio-aberto. No estado fechado, o tráfego flui normalmente enquanto erros são contabilizados. Quando o limite de falhas é atingido, ele transiciona para o estado aberto, bloqueando as chamadas e retornando uma resposta padrão ou fallback imediata. Após um período de espera, o disjuntor entra no estado meio-aberto, permitindo que uma única requisição de teste passe para verificar se o serviço recuperou a saúde.

A implementação correta evita o comportamento de tempestade de retentativas, onde milhares de clientes tentam reconectar simultaneamente a um servidor que acabou de voltar do ar. Ao retornar um erro rápido, o cliente entende que deve aguardar ou exibir uma interface alternativa, aliviando a pressão sobre a infraestrutura debilitada.

Implementação Prática e Ajuste Dinâmico de Limiares

Para ilustrar a aplicação prática desses conceitos, podemos analisar um trecho de código em Java utilizando uma biblioteca de resiliência padrão de mercado. A configuração define limites estritos de execução concorrente e políticas de falha baseadas em percentual de erros acumulados em uma janela de tempo deslizante.

BulkheadConfig bulkheadConfig = BulkheadConfig.custom().maxConcurrentCalls(25).maxWaitDuration(Duration.ofMillis(500)).build();CircuitBreakerConfig breakerConfig = CircuitBreakerConfig.custom().failureRateThreshold(50.0).slowCallRateThreshold(50.0).slowCallDurationThreshold(Duration.ofSeconds(2)).waitDurationInOpenState(Duration.ofSeconds(10)).slidingWindowType(SlidingWindowType.COUNT_BASED).slidingWindowSize(100).build();

O código acima configura um bulkhead que permite no máximo vinte e cinco chamadas simultâneas, rejeitando novas tentativas após quinhentos milissegundos de espera. Paralelamente, o circuit breaker monitora uma janela de cem chamadas, desarmando caso a taxa de falhas ou de lentidão ultrapasse cinquenta por cento, permanecendo aberto por dez segundos antes de permitir novas tentativas de sondagem.

Em ambientes de produção dinâmicos, manter esses valores estáticos pode gerar falsos positivos ou lentidão na detecção de falhas. Sistemas modernos utilizam telemetria em tempo real para ajustar os limiares com base no comportamento histórico do tráfego, elevando a tolerância durante horários de pico e apertando os critérios em janelas de manutenção ou baixa atividade.

Considerações Finais sobre Arquiteturas Resilientes

Construir softwares tolerantes a falhas exige uma mudança profunda de mentalidade: deixar de focar exclusivamente em evitar que erros aconteçam para planejar como o sistema deve se comportar quando eles inevitavelmente ocorrerem. A combinação de bulkheads para isolar recursos e circuit breakers para conter cascatas de erros forma a espinha dorsal de qualquer arquitetura distribuída de missão crítica.

Embora esses padrões adicionem uma camada extra de complexidade de configuração e monitoramento, o retorno sobre o investimento se paga na primeira interrupção de grande escala evitada. Ao adotar uma abordagem pragmática, instrumentando métricas claras e testando continuamente os limites da infraestrutura, garantimos uma experiência estável e confiável para os usuários finais, independentemente das instabilidades subjacentes na rede.