Circuit breaker e resiliência
1. Introdução ao Circuit Breaker
Em sistemas distribuídos, falhas são inevitáveis. Um serviço pode ficar lento, indisponível ou retornar erros inesperados. O padrão Circuit Breaker — ou disjuntor — atua como um mecanismo de proteção que interrompe temporariamente as chamadas a um serviço com falhas, evitando que o sistema inteiro seja arrastado para baixo.
A analogia é direta com um disjuntor elétrico: quando a corrente ultrapassa um limite seguro, o disjuntor "abre" o circuito, interrompendo o fluxo. Em software, quando a taxa de falhas de uma chamada remota excede um threshold configurado, o circuito abre e as requisições subsequentes são recusadas imediatamente, sem tentar a chamada real.
Por que isso é crítico? Em arquiteturas de microsserviços, uma única falha pode se propagar em cascata. Se o Serviço A depende do Serviço B, e B começa a responder lentamente, A pode acumular threads e conexões, até exaurir seus próprios recursos. Esse efeito dominó é conhecido como falha em cascata e pode derrubar todo o ecossistema.
O Circuit Breaker não atua sozinho. Ele se combina com outros padrões de resiliência:
- Retry: tentar novamente operações que falharam, mas com backoff exponencial
- Timeout: limitar o tempo máximo de espera por uma resposta
- Bulkhead: isolar recursos para que falhas em um componente não afetem outros
Juntos, esses padrões formam a base de um sistema resiliente.
2. Estados e Ciclo de Vida do Circuit Breaker
O Circuit Breaker clássico possui três estados:
Fechado (Closed)
- Estado normal de operação
- Todas as requisições são encaminhadas ao serviço alvo
- Um contador de falhas é incrementado a cada erro
- Quando o contador atinge o threshold de falhas (ex.: 5 falhas consecutivas), o circuito transita para Aberto
Aberto (Open)
- Requisições são recusadas imediatamente, sem chamar o serviço
- Uma exceção de circuito aberto é lançada ou um fallback é acionado
- Permanece aberto por um período configurável (timeout duration)
- Após esse período, transita para Meio-Aberto
Meio-Aberto (Half-Open)
- Estado de teste: um número limitado de requisições é permitido
- Se essas requisições forem bem-sucedidas, o circuito retorna a Fechado
- Se falharem, o circuito volta a Aberto e o timeout é reiniciado
Parâmetros configuráveis típicos:
failureCountThreshold: 5
timeoutDuration: 10 segundos
halfOpenMaxRequests: 3
halfOpenRetryInterval: 5 segundos
A escolha desses parâmetros depende da criticidade do serviço e do comportamento esperado das falhas. Um serviço crítico pode ter thresholds mais baixos para reagir rapidamente; um serviço tolerante pode permitir mais falhas antes de abrir o circuito.
3. Implementação Prática do Circuit Breaker
Estrutura básica em Python (sem biblioteca)
import time
from enum import Enum
class CircuitState(Enum):
CLOSED = 1
OPEN = 2
HALF_OPEN = 3
class CircuitBreaker:
def __init__(self, failure_threshold=5, recovery_timeout=10, half_open_max=3):
self.state = CircuitState.CLOSED
self.failure_count = 0
self.failure_threshold = failure_threshold
self.recovery_timeout = recovery_timeout
self.half_open_max = half_open_max
self.half_open_attempts = 0
self.last_failure_time = None
def call(self, func, *args, **kwargs):
if self.state == CircuitState.OPEN:
if time.time() - self.last_failure_time >= self.recovery_timeout:
self.state = CircuitState.HALF_OPEN
self.half_open_attempts = 0
else:
raise Exception("Circuit is open")
try:
result = func(*args, **kwargs)
self._on_success()
return result
except Exception as e:
self._on_failure()
raise e
def _on_success(self):
if self.state == CircuitState.HALF_OPEN:
self.half_open_attempts += 1
if self.half_open_attempts >= self.half_open_max:
self.state = CircuitState.CLOSED
self.failure_count = 0
else:
self.failure_count = 0
def _on_failure(self):
self.failure_count += 1
self.last_failure_time = time.time()
if self.failure_count >= self.failure_threshold:
self.state = CircuitState.OPEN
Integração com chamadas HTTP e fallback
import requests
def call_external_api():
response = requests.get("https://api.exemplo.com/dados", timeout=5)
response.raise_for_status()
return response.json()
def fallback_response():
return {"status": "unavailable", "data": None}
breaker = CircuitBreaker(failure_threshold=3, recovery_timeout=15)
try:
result = breaker.call(call_external_api)
except Exception:
result = fallback_response()
Uso com Resilience4j (Java)
// Configuração via código
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
.failureRateThreshold(50)
.waitDurationInOpenState(Duration.ofSeconds(10))
.slidingWindowSize(5)
.build();
CircuitBreakerRegistry registry = CircuitBreakerRegistry.of(config);
CircuitBreaker breaker = registry.circuitBreaker("servicoA");
// Decorando uma chamada
Supplier<String> decorated = CircuitBreaker.decorateSupplier(breaker, () -> {
return restTemplate.getForObject("http://servico-a/api", String.class);
});
// Com fallback
String result = Try.ofSupplier(decorated)
.recover(throwable -> "Fallback: serviço indisponível")
.get();
4. Monitoramento e Métricas do Circuit Breaker
Um Circuit Breaker sem monitoramento é uma caixa-preta. Métricas essenciais incluem:
- Taxa de falhas: percentual de chamadas que falharam na janela atual
- Tempo médio de resposta: latência das chamadas bem-sucedidas
- Estado atual: fechado, aberto ou meio-aberto
- Número de requisições recusadas: quando o circuito está aberto
- Tempo desde a última transição: ajuda a entender padrões de falha
Logging e alertas
# Exemplo de log estruturado
{
"event": "circuit_state_change",
"service": "servico-pagamentos",
"from_state": "CLOSED",
"to_state": "OPEN",
"failure_count": 5,
"timestamp": "2025-03-20T14:30:00Z"
}
Alertas devem ser configurados para:
- Circuito abre: notificar equipe de plantão
- Circuito permanece aberto por mais de X minutos: escalar para engenharia
- Circuito alterna entre estados com frequência: indicar instabilidade
Integração com Prometheus e Grafana
Bibliotecas como Resilience4j expõem métricas automaticamente via Micrometer. Um dashboard típico pode mostrar:
# Métricas expostas (Prometheus)
resilience4j_circuitbreaker_state{name="servicoA", state="closed"} 1
resilience4j_circuitbreaker_calls_total{name="servicoA", kind="success"} 120
resilience4j_circuitbreaker_calls_total{name="servicoA", kind="failure"} 8
5. Estratégias de Fallback e Degradação Graciosa
Quando o circuito abre, o sistema não deve simplesmente falhar. É aqui que entram as estratégias de fallback.
Fallback estático vs. dinâmico
Fallback estático: retorna uma resposta pré-definida, como um JSON com status de erro.
def fallback_estatico():
return {"status": "error", "message": "Serviço temporariamente indisponível"}
Fallback dinâmico: utiliza dados em cache ou respostas parciais de fontes alternativas.
cache_local = {
"ultimo_preco": 150.75,
"timestamp": "2025-03-20T12:00:00Z"
}
def fallback_dinamico():
if cache_local:
return {"status": "degraded", "data": cache_local["ultimo_preco"]}
return {"status": "unavailable"}
Degradação graciosa
Em vez de falhar completamente, o sistema reduz funcionalidades. Por exemplo:
- Serviço de recomendações offline: exibe apenas produtos populares em vez de recomendações personalizadas
- Serviço de pagamentos indisponível: permite apenas pagamentos com cartão (modo offline), bloqueando PIX e boleto
def obter_recomendacoes(usuario_id):
try:
return breaker.call(recomendacoes_api, usuario_id)
except Exception:
# Degradação: retorna recomendações genéricas
return obter_produtos_mais_vendidos()
6. Armadilhas e Boas Práticas
Armadilhas comuns
- Timeouts mal configurados: um timeout muito longo (ex.: 60s) pode fazer o circuito demorar para abrir, mantendo threads bloqueadas
- Circuitos muito sensíveis: abrir após 2 falhas em uma janela de 1 minuto pode causar abertura desnecessária durante picos normais de erro
- Circuitos muito insensíveis: permitir 100 falhas antes de abrir pode causar danos significativos ao sistema
- Granularidade inadequada: um único circuito para todo um serviço pode mascarar falhas em endpoints específicos
Boas práticas
- Granularidade correta: um Circuit Breaker por endpoint ou operação crítica, não por serviço inteiro
- Testes de resiliência: usar chaos engineering para simular falhas (ex.: Netflix Chaos Monkey)
- Configuração baseada em SLA: thresholds devem refletir o nível de serviço acordado
- Monitoramento contínuo: ajustar parâmetros com base em dados reais de produção
7. Integração com Microsserviços e Ecossistema
Em arquiteturas de microsserviços, o Circuit Breaker é uma peça central da resiliência. Ele se integra com:
Service Discovery e Load Balancing
Quando um serviço está com o circuito aberto, o load balancer pode ser notificado para evitar rotear requisições para instâncias problemáticas. Combinado com health checks, o sistema pode remover automaticamente instâncias degradadas do pool.
# Exemplo com Kubernetes e service mesh (Istio)
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: servico-pagamentos
spec:
host: servico-pagamentos
trafficPolicy:
connectionPool:
http:
http1MaxPendingRequests: 10
maxRequestsPerConnection: 5
outlierDetection:
consecutive5xxErrors: 3
interval: 30s
baseEjectionTime: 30s
Relação com o padrão Strangler Fig
Durante a migração de um monolito para microsserviços, o Circuit Breaker ajuda a isolar falhas. Se o novo microsserviço falhar, o circuito abre e o fallback pode redirecionar para a funcionalidade legada, garantindo continuidade operacional.
def obter_dados_cliente(cliente_id):
try:
return breaker.call(novo_microsservico, cliente_id)
except Exception:
# Fallback para o monolito legado
return monolito_legado.obter_dados(cliente_id)
Referências
- Resilience4j Documentation — Documentação oficial da biblioteca Resilience4j com exemplos de configuração e uso do Circuit Breaker em Java
- Microsoft - Circuit Breaker Pattern — Guia oficial da Microsoft sobre o padrão Circuit Breaker em arquiteturas cloud
- Martin Fowler - CircuitBreaker — Artigo seminal de Martin Fowler explicando o padrão e suas variações
- Netflix TechBlog - Hystrix — Post original da Netflix sobre o Hystrix, biblioteca que popularizou o Circuit Breaker
- Istio - Circuit Breaking — Documentação do Istio sobre circuit breaking em service mesh para microsserviços
- AWS - Resilience Patterns — Padrões de resiliência da AWS Well-Architected Framework, incluindo Circuit Breaker
- Gremlin - Chaos Engineering — Tutorial sobre como testar Circuit Breakers com Chaos Engineering