Marcio Cunha

Como criar microsserviços resilientes com circuit breaker

Aprenda como funcionam o circuit breaker e o fallback na comunicação síncrona de microsserviços para evitar falhas em cascata em ambientes de produção.

Marcio Cunha•4 min
Também disponível em:EnglishEspañol
Resumo
  • Sistemas distribuídos falham frequentemente devido à alta interdependência entre componentes síncronos.
  • O padrão circuit breaker interrompe chamadas repetidas para serviços instáveis, protegendo o sistema de sobrecarga.
  • Estratégias de fallback garantem respostas alternativas ou degradação graciosa quando dependências externas caem.
  • O monitoramento contínuo de latência e taxas de erro define os limiares ideais de transição de estados.
  • Integrar resiliência exige combinar isolamento de falhas com estratégias complementares como limitação de requisições .

O perigo silencioso das falhas em cascata em sistemas distribuídos

Quando separamos uma aplicação monolítica em vários microsserviços, ganhamos agilidade e capacidade de escalar partes específicas de forma isolada. No entanto, introduzimos uma dependência crítica: a comunicação síncrona via rede. Na prática, isso significa que se o serviço de pagamentos demora para responder, a aplicação principal continua acumulando requisições, travando threads e esgotando recursos até que todo o sistema caia como um baralho de cartas.

Essa reação em cadeia é o pesadelo de qualquer engenheiro de software em ambientes de produção. Para combater esse problema, precisamos adotar padrões arquiteturais focados em tolerância a falhas. Assim como um disjuntor elétrico desarma a casa quando há uma sobrecarga para evitar um incêndio, no desenvolvimento de software usamos o mecanismo de circuit breaker para cortar o fluxo de chamadas antes que o caos se instale.

Como funciona o circuit breaker na prática

O circuit breaker opera monitorando continuamente a saúde das chamadas externas feitas para outro serviço. Ele possui essencialmente três estados operacionais: fechado, aberto e meio-aberto. Na prática, quando o disjuntor está fechado, as requisições passam livremente. Se a taxa de erros ou o tempo limite de resposta ultrapassam um limite pré-configurado, o disjuntor muda para o estado aberto.

Com o circuito aberto, novas chamadas para o serviço problemático falham imediatamente de forma local, sem sequer tentar abrir uma conexão de rede inútil. Isso poupa recursos preciosos da CPU e evita que a aplicação dependente continue sobrecarregando um servidor que já está à beira do colapso. Após um período de espera estipulado, o componente entra em estado meio-aberto, permitindo que uma única requisição de teste passe para verificar se o serviço externo se recuperou.

Implementando fallback para garantir degradação graciosa

Cortar o tráfego com o circuit breaker evita a sobrecarga, mas deixa a interface do usuário ou a API consumidora sem uma resposta útil. É aqui que entra o conceito de fallback, que atua como um plano de B estratégico. Na prática, o fallback é uma rota alternativa de código executada sempre que a chamada principal falha ou quando o disjuntor está aberto.

Se um microsserviço de recomendação de produtos falha, por exemplo, o sistema pode retornar uma lista estática de itens mais vendidos ou exibir dados armazenados em cache local em vez de quebrar a página inteira para o cliente. Essa abordagem é conhecida como degradação graciosa: o usuário percebe uma perda menor de funcionalidade, mas o serviço principal permanece totalmente operacional e estável.

Código de exemplo para estruturar a proteção

Para ilustrar a implementação prática, imagine o uso de uma biblioteca de resiliência em um serviço corporativo. O trecho abaixo demonstra a configuração básica de um circuit breaker atrelado a uma função de fallback em ambiente Node.js, protegendo uma chamada HTTP crítica.

const CircuitBreaker = require('opossum');

async function chamarServicoPagamento() {
  // Lógica de comunicação síncrona com o serviço externo
  return fetch('https://pagamentos.interno/api');
}

const options = {
  timeout: 3000, // Se demorar mais de 3 segundos, falha
  errorThresholdPercentage: 50, // Se 50% falharem, abre o circuito
  resetTimeout: 10000 // Tenta de novo após 10 segundos
};

const breaker = new CircuitBreaker(chamarServicoPagamento, options);

breaker.fallback(() => 'Serviço indisponível no momento. Tente mais tarde.');

breaker.fire()
  .then(console.log)
  .catch(console.error);

Esse código encapsula a chamada de rede de forma que qualquer anomalia seja tratada de imediato. Além de proteger a infraestrutura com o circuit breaker, vale lembrar que a estabilidade geral do sistema se beneficia muito de camadas adicionais de defesa, como o controle rigoroso de requisições na borda para evitar abusos acidentais ou intencionais.

Decisões de design e trade-offs operacionais

Adotar padrões de tolerância a falhas exige escolhas conscientes de arquitetura. O principal trade-off reside na complexidade adicional de monitoramento e no comportamento imprevisível que pode surgir caso os tempos de espera sejam mal configurados. Se o limite de timeout for curto demais, você poderá abrir o circuito prematuramente devido a oscilações normais da rede. Se for longo demais, o sistema continuará travando.

Outro ponto crítico é decidir qual deve ser o comportamento do fallback. Em operações de leitura, retornar cache ou dados padrão é relativamente seguro. Porém, em operações de escrita ou transações financeiras, executar um fallback incorreto pode gerar inconsistências de dados difíceis de rastrear. Por isso, a engenharia deve alinhar com o negócio exatamente qual é o impacto aceitável quando um microsserviço secundário fica inacessível.

Conclusão e próximos passos para produção

Construir microsserviços resilientes não significa apenas escrever código funcional, mas preparar a aplicação para falhar de maneira elegante quando o ambiente ao seu redor colapsar. A combinação correta de circuit breakers, fallbacks inteligentes e monitoramento constante transforma sistemas frágeis em arquiteturas robustas capazes de absorver impactos semderrubar a experiência do usuário final.

À medida que sua infraestrutura evolui, vale a pena expandir essas práticas para outras frentes da engenharia de software, garantindo que cada componente da sua cadeia tecnológica seja construído com a premissa de que a falha na rede é uma certeza inevitável.