Como implementar circuit breaker em microservices
1. Fundamentos do Circuit Breaker em Microservices
O padrão Circuit Breaker é um mecanismo de tolerância a falhas projetado para proteger sistemas distribuídos contra falhas em cascata. Inspirado nos disjuntores elétricos, ele monitora chamadas remotas e interrompe automaticamente as requisições quando a taxa de falhas ultrapassa um limite predefinido. Seu propósito principal é evitar que um serviço sobrecarregado ou indisponível consuma recursos desnecessários, permitindo que o sistema se recupere gradualmente.
Os estados clássicos do Circuit Breaker são:
- Fechado (Closed): Estado normal, onde as requisições fluem livremente. Falhas são contadas até atingir um limite.
- Aberto (Open): Quando o limite de falhas é atingido, o circuito abre e todas as requisições são rejeitadas imediatamente, sem tentar a chamada real.
- Semi-aberto (Half-Open): Após um tempo de espera, o circuito permite um número limitado de requisições de teste para verificar se o serviço se recuperou.
É crucial diferenciar Circuit Breaker de outros padrões: Retry tenta repetir uma operação que falhou, enquanto Timeout define um limite de tempo para uma chamada. O Circuit Breaker atua em um nível mais alto, prevenindo chamadas desnecessárias quando um serviço está claramente degradado.
2. Identificando Cenários Críticos para Aplicação
Os cenários mais comuns que demandam Circuit Breaker incluem:
- Dependências externas instáveis: APIs de terceiros, gateways de pagamento, provedores de autenticação.
- Serviços com latência variável: Bancos de dados sob carga, filas de mensagens congestionadas.
- Cadeias de chamadas encadeadas: Quando o Serviço A chama o B, que chama o C. Uma falha em C pode travar toda a cadeia.
O efeito cascata é particularmente perigoso: se o Serviço A fica esperando uma resposta do B (que está lento), os threads de A se esgotam, causando falhas em cascata para outros serviços que dependem de A.
3. Projetando a Lógica de Transição de Estados
A transição entre estados depende de parâmetros bem definidos:
Thresholds de falha:
- Contagem absoluta: Exemplo: 5 falhas consecutivas.
- Janela de tempo: Exemplo: 10 falhas em 60 segundos.
- Taxa de erro: Exemplo: 50% de falhas nos últimos 100 requests.
Tempo de espera no estado Aberto: Geralmente entre 5 e 30 segundos, dependendo do tempo esperado de recuperação do serviço.
Estratégia para estado Semi-aberto:
- Número de requisições de sonda: Exemplo: 3 requisições de teste.
- Contagem de sucessos para fechar: Se todas as 3 sondas forem bem-sucedidas, o circuito fecha.
- Se alguma sonda falhar, o circuito volta ao estado Aberto e reinicia o timer.
4. Implementando o Circuit Breaker com Código Genérico
Abaixo, um exemplo de implementação simples em Python usando contadores e timers:
import time
import threading
class CircuitBreaker:
def __init__(self, failure_threshold=5, recovery_timeout=10, half_open_max_requests=3):
self.failure_threshold = failure_threshold
self.recovery_timeout = recovery_timeout
self.half_open_max_requests = half_open_max_requests
self.state = 'CLOSED'
self.failure_count = 0
self.last_failure_time = None
self.half_open_requests = 0
self.lock = threading.Lock()
def call(self, func, *args, **kwargs):
with self.lock:
if self.state == 'OPEN':
if time.time() - self.last_failure_time > self.recovery_timeout:
self.state = 'HALF_OPEN'
self.half_open_requests = 0
else:
raise Exception('Circuit breaker is OPEN')
if self.state == 'HALF_OPEN':
if self.half_open_requests >= self.half_open_max_requests:
raise Exception('Circuit breaker is HALF_OPEN, max test requests reached')
self.half_open_requests += 1
try:
result = func(*args, **kwargs)
with self.lock:
if self.state == 'HALF_OPEN':
self.state = 'CLOSED'
self.failure_count = 0
elif self.state == 'CLOSED':
self.failure_count = 0
return result
except Exception as e:
with self.lock:
self.failure_count += 1
self.last_failure_time = time.time()
if self.failure_count >= self.failure_threshold:
self.state = 'OPEN'
raise e
# Exemplo de uso
cb = CircuitBreaker(failure_threshold=3, recovery_timeout=5)
def chamada_remota():
# Simula uma chamada que falha
raise ConnectionError('Serviço indisponível')
for i in range(5):
try:
cb.call(chamada_remota)
except Exception as e:
print(f"Tentativa {i+1}: {e}")
Para integração com bibliotecas reais, Resilience4j (Java) e Polly (.NET) são amplamente utilizadas. Exemplo com Resilience4j:
// Configuração em Java com Resilience4j
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
.failureRateThreshold(50)
.waitDurationInOpenState(Duration.ofSeconds(10))
.slidingWindowSize(100)
.build();
CircuitBreaker circuitBreaker = CircuitBreaker.of("meu-servico", config);
// Uso com decorador
Supplier<String> supplier = () -> chamadaRemota();
Supplier<String> decorated = CircuitBreaker.decorateSupplier(circuitBreaker, supplier);
String resultado = Try.ofSupplier(decorated).get();
5. Configuração e Políticas de Timeout e Retry
Combinar Circuit Breaker com Timeout é essencial: um timeout impede que uma chamada fique pendente indefinidamente, enquanto o Circuit Breaker evita que chamadas sejam tentadas quando o serviço está instável.
Políticas de retry inteligentes:
- Backoff exponencial: espera 1s, 2s, 4s, 8s entre tentativas.
- Jitter: adiciona aleatoriedade para evitar tempestade de retries.
Ordem de execução: Geralmente, o Retry deve ser executado antes do Circuit Breaker. Isso permite que falhas temporárias sejam resolvidas com retries, sem abrir o circuito. Se o retry falhar, aí sim o Circuit Breaker conta a falha.
6. Monitoramento e Métricas Essenciais
Métricas fundamentais para coletar:
- Taxa de falha: Percentual de chamadas que falham.
- Tempo de abertura: Duração que o circuito permanece aberto.
- Requisições rejeitadas: Número de chamadas bloqueadas pelo circuito.
- Estado atual: Fechado, Aberto ou Semi-aberto.
Para expor em dashboards (Prometheus + Grafana), utilize exporters específicos. Exemplo de métrica exposta:
# HELP circuit_breaker_state Estado atual do circuit breaker (0=CLOSED, 1=OPEN, 2=HALF_OPEN)
# TYPE circuit_breaker_state gauge
circuit_breaker_state{service="payment-api"} 0
circuit_breaker_failure_rate{service="payment-api"} 0.23
circuit_breaker_rejected_requests_total{service="payment-api"} 150
Alertas devem ser configurados para:
- Mudança de estado para OPEN.
- Taxa de falha acima de 50% por mais de 5 minutos.
- Número excessivo de requisições rejeitadas.
7. Tratamento de Falhas e Fallbacks
Estratégias de fallback comuns:
- Resposta padrão: Retornar um valor default (ex: lista vazia, preço padrão).
- Cache: Usar dados em cache de respostas anteriores bem-sucedidas.
- Degradação funcional: Desabilitar funcionalidades não essenciais.
Exemplo de fallback com Resilience4j:
Supplier<String> fallback = () -> "Resposta de fallback";
Supplier<String> decorated = CircuitBreaker.decorateSupplier(circuitBreaker, supplier)
.withFallback(fallback);
Logs estruturados são vitais para debugging:
{
"event": "circuit_breaker_opened",
"service": "payment-gateway",
"failure_count": 5,
"timestamp": "2025-01-15T10:30:00Z"
}
8. Boas Práticas e Armadilhas Comuns
Boas práticas:
- Isolamento por endpoint ou operação: Cada dependência externa deve ter seu próprio Circuit Breaker.
- Testes de caos: Simule falhas em produção para validar o comportamento.
- Configuração baseada em SLA: Ajuste thresholds conforme o tempo de resposta esperado.
Armadilhas comuns:
- Circuit breakers aninhados: Se o Serviço A tem um CB para B, e B tem um CB para C, uma falha em C pode abrir ambos, criando loops de falha.
- Thresholds muito baixos: Podem abrir o circuito com poucas falhas, causando falsos positivos.
- Esquecer de resetar contadores: Após recuperação, os contadores devem ser zerados.
Implementar Circuit Breaker corretamente é uma das práticas mais eficazes para aumentar a resiliência de sistemas baseados em microservices. Quando combinado com monitoramento adequado e estratégias de fallback, ele transforma falhas inevitáveis em eventos controlados, mantendo a experiência do usuário estável.
Referências
- Resilience4j Documentation - CircuitBreaker — Documentação oficial da biblioteca Resilience4j com exemplos de configuração e uso do Circuit Breaker em Java.
- Microsoft Docs - Polly Circuit Breaker — Guia da Microsoft sobre implementação do padrão Circuit Breaker com a biblioteca Polly em .NET.
- Martin Fowler - CircuitBreaker — Artigo seminal de Martin Fowler explicando o padrão Circuit Breaker e suas variações.
- Netflix Tech Blog - Making the Netflix API More Resilient — Post do blog técnico da Netflix sobre como implementaram Circuit Breaker no ecossistema de microservices.
- Baeldung - Introduction to Hystrix — Tutorial prático sobre Hystrix, a biblioteca pioneira de Circuit Breaker para Java, com exemplos de código.
- Grafana Labs - Monitoring Circuit Breakers with Prometheus — Guia sobre como monitorar Circuit Breakers usando Prometheus e Grafana, com métricas e dashboards.