Marcio Cunha

Como criar um sistema distribuído resiliente a falhas

Descubra como projetar microsserviços tolerantes a falhas usando padrões de circuit breaker, isolamento com bulkheads e mensageria assíncrona.

Marcio Cunha•5 min
Também disponível em:EspañolEnglish
Resumo
  • Sistemas distribuídos enfrentam falhas de rede e quedas parciais de forma inevitável.
  • Circuit breakers evitam que falhas em cascata derrubem todo o ecossistema de serviços.
  • Compartimentos isolados impedem que um único módulo esgote recursos cruciais.
  • Mensageria assíncrona garante desacoplamento e entrega confiável de dados.
  • Resiliência exige planejamento ativo contra cenários de indisponibilidade.

Por que sistemas distribuídos quebram de repente

Quando dividimos uma aplicação monolítica em dezenas de microsserviços independentes, ganhamos agilidade e capacidade de escala, mas herdamos a complexidade inerente às redes de computadores. Na prática, isso significa que a internet deixa de ser um canal perfeitamente confiável e passa a introduzir latência, pacotes perdidos e quedas abruptas de conexão. Se um único serviço de pagamento demora para responder, a lentidão se espalha como um dominó para a vitrine da loja e o carrinho de compras. Para evitar esse efeito dominó, precisamos adotar estratégias arquiteturais que aceitem a falha como um evento normal e esperado do ciclo de vida operacional.

Em um ambiente monolítico tradicional, as chamadas entre funções acontecem na memória RAM da máquina, com tempo de resposta quase instantâneo e sem risco de interrupção por falhas de cabo ou roteador. Já nos sistemas distribuídos, cada chamada exige uma viagem pela rede, abrindo margem para timeouts e falhas parciais difíceis de prever. É justamente nesse cenário desafiador que a engenharia de resiliência entra em cena, buscando conter os estragos antes que o usuário final perceba qualquer instabilidade no sistema. Para entender melhor como isolar problemas complexos de infraestrutura, vale a pena conferir estratégias avançadas na nossa análise sobre como usar o padrão Saga para garantir consistência em microsserviços, mantendo a integridade dos dados mesmo quando transações demoram a concluir.

Como funciona o circuit breaker na prática

O padrão de projeto conhecido como disjuntor de circuito, ou circuit breaker, funciona exatamente como a chave térmica instalada no quadro de luz da nossa casa. Na prática, quando um eletrodoméstico apresenta curto-circuito, o disjuntor desliga sozinho para proteger toda a fiação elétrica de um incêndio catastrófico. No universo dos microsserviços, o circuit breaker monitora constantemente as chamadas feitas a um serviço externo e, se a taxa de erros ultrapassa um limite tolerável, ele abre o circuito e bloqueia novas tentativas por um período. Em vez de insistir em requisições que sabemos que vão falhar e sobrecarregar ainda mais o servidor de destino, o sistema retorna uma resposta padrão imediata ou aciona uma alternativa de contingência.

Esse mecanismo possui tipicamente três estados operacionais: fechado, aberto e meio-aberto. No estado fechado, o fluxo de requisições transcorre normalmente enquanto o monitoramento contabiliza eventuais falhas. Quando os erros acumulados estouram o limite configurado, o disjuntor muda para o estado aberto, interceptando qualquer nova chamada antes mesmo que ela saia da aplicação. Após um tempo de espera estipulado, ele transita para o estado meio-aberto, permitindo que uma única requisição de teste passe para verificar se o serviço problemático já se recuperou. Se a requisição de teste for bem-sucedida, o circuito se fecha novamente; caso contrário, volta ao estado aberto por mais alguns instantes.

Isolando falhas com o padrão bulkhead

Imagine um grande navio de carga estruturado em vários compartimentos estanques chamados de bulkheads, concebidos para reter a água caso o casco sofra uma perfuração em alto mar. Na engenharia de software, o conceito de bulkhead aplica exatamente essa mesma lógica de isolamento físico aos recursos computacionais dos nossos microsserviços. Na prática, isso significa que separamos as conexões de rede, as threads de processamento e as filas de memória em pools exclusivos para cada dependência externa. Se o serviço de recomendação de produtos ficar totalmente indisponível e esgotar todas as suas threads dedicadas, o restante da aplicação continua funcionando perfeitamente porque as suas próprias threads não foram afetadas pelo problema.

Sem o uso de bulkheads, uma falha isolada em um serviço periférico de baixa criticidade tem o potencial de consumir todas as conexões simultâneas do pool principal da aplicação. Quando isso ocorre, o servidor esgota seus descritores de arquivo e memória, derrubando funcionalidades essenciais como o login e o checkout de clientes. Ao limitar o raio de explosão de cada dependência, garantimos que o pior cenário resulte apenas na degradação elegante de uma funcionalidade secundária, preservando a experiência geral de navegação do usuário. Para aprofundar o conhecimento sobre otimização de recursos sob pressão extrema, consulte também nosso guia técnico detalhado sobre banco de dados lento e como otimizar queries com EXPLAIN ANALYZE em ambientes de alta concorrência.

Mensageria assíncrona para desacoplar sistemas

A comunicação síncrona entre microsserviços, feita através de requisições HTTP diretas, cria um forte acoplamento temporal que compromete a estabilidade de toda a cadeia operacional. Na prática, a mensageria assíncrona resolve esse problema inserindo um intermediário confiável, como um broker de mensagens, entre o produtor e o consumidor dos dados. Em vez de enviar um pedido e ficar aguardando travado a resposta do outro serviço, a aplicação publica uma mensagem em um tópico ou fila e prossegue imediatamente com suas tarefas. O serviço receptor processa essa mensagem no seu próprio ritmo, garantindo que picos repentinos de tráfego sejam absorvidos sem causar quedas por sobrecarga de conexões simultâneas.

Esse modelo baseado em eventos traz uma robustez impressionante para arquiteturas modernas, pois se o microsserviço consumidor cair temporariamente para manutenção, as mensagens ficam guardadas com segurança no broker. Assim que o serviço volta ao ar, ele retoma o processamento de onde parou, sem perda de dados ou necessidade de novas tentativas manuais por parte do cliente. Contudo, essa liberdade assíncrona exige atenção redobrada com a idempotência das operações, assegurando que o reprocessamento acidental de uma mesma mensagem não gere efeitos colaterais indesejados, como cobranças duplicadas em contas de usuários ou envios repetidos de notificações.

Considerações finais sobre resiliência operacional

Desenvolver sistemas distribuídos resilientes exige uma mudança profunda de mentalidade, onde assumimos que a falha de hardware, rede ou software é apenas uma questão de tempo. Ao combinar o uso inteligente de circuit breakers, o isolamento rigoroso de recursos por meio de bulkheads e a flexibilidade da mensageria assíncrona, construímos arquiteturas capazes de absorver impactos severos. O objetivo final nunca é eliminar completamente os erros do sistema, mas sim garantir que a aplicação saiba lidar com eles de forma elegante, mantendo o negócio funcionando e os clientes satisfeitos mesmo sob condições adversas.