Arquitetura real · Sênior
Netflix: circuit breakers e falha em cascata (Hystrix)
Arquitetura real em produção. Pontos centrais: hystrix em manutenção desde 2018 e o padrão continua válido e o sucessor citado é o Resilience4j.
Resposta curta
Arquitetura real em produção. Pontos centrais: hystrix em manutenção desde 2018 e o padrão continua válido e o sucessor citado é o Resilience4j. Este caso reúne o que o Netflix publicou sobre como protege as chamadas da sua API a outros serviços.
01 · CONTEXTO
Dezenas de dependências e uma só delas lenta
Este caso reúne o que o Netflix publicou sobre como protege as chamadas da sua API a outros serviços. A API recebia mais de 1 bilhão de chamadas por dia, que se desdobravam em vários bilhões de chamadas a dezenas de subsistemas.
Segundo o post, sem medidas de tolerância a falhas, 30 dependências com 99,99% de disponibilidade cada resultariam em mais de 2 horas de indisponibilidade por mês.
O post chama a dependência latente de pior tipo de falha para um subsistema: uma única dependência lenta, em alto volume, bloqueia threads de requisição e pode saturar rapidamente todas as threads do contêiner, derrubando a API inteira.
O usuário chama a API, que por sua vez chama duas dependências pela rede.
Uma das dependências passa a responder com latência maior.
As requisições que esperam por essa dependência mantêm threads de requisição da API bloqueadas.
Com alto volume, as threads de requisição do contêiner podem se esgotar rapidamente e a API inteira cai.
02 · ISOLAMENTO
Um compartimento por dependência
A resposta do Hystrix começa pelo padrão bulkhead (anteparo): isolar as dependências entre si e limitar o acesso concorrente a cada uma. A documentação cita bulkhead, swimlane e circuit breaker como técnicas de isolamento para limitar o impacto de qualquer dependência.
Na prática, cada chamada de dependência vai envolvida em um comando e cada dependência ganha um pool de threads próprio, de modo que a latência de uma só satura as threads do pool dela.
A API envolve cada chamada de dependência em um comando.
Cada dependência tem o seu próprio pool de threads.
A dependência B fica lenta e só as threads do pool dela se esgotam; as threads de requisição da API expiram ou são rejeitadas na hora, sem bloquear.
Com o pool cheio, as novas chamadas destinadas à B são rejeitadas imediatamente em vez de enfileiradas.
O fallback devolve uma resposta alternativa, como dados em cache, em vez de um erro.
As chamadas à dependência A seguem pelo pool dela, sem depender do pool saturado de B.
Por que threads? A documentação reconhece que dá para se proteger sem pools de threads, mas isso exige confiar que o cliente falhe muito rápido e se comporte bem sempre. Como as bibliotecas cliente tendem a ser caixas-pretas, opacas quanto a detalhes de implementação, padrões de acesso à rede e configurações padrão, a equipe concluiu que, na maioria dos casos, os benefícios de isolar as chamadas em threads separadas superam as desvantagens.
03 · CIRCUIT BREAKER
Parar de tentar o que provavelmente vai falhar
Segundo Martin Fowler, chamadas remotas podem falhar ou ficar sem resposta até estourar um timeout, e Michael Nygard popularizou o padrão circuit breaker para evitar essa cascata. A ideia do padrão é simples: depois que as falhas atingem um limiar, o breaker dispara e as chamadas seguintes retornam erro.
No Netflix, o circuito dispara quando o comando passa de um limiar de erros, por exemplo 50% em uma janela de 10 segundos, e então rejeita todas as requisições até que verificações de saúde tenham sucesso.
O objetivo principal é aliviar a pressão sobre os sistemas com problema e reduzir a latência da requisição, falhando rápido ou devolvendo um fallback quando a falha é provável.
Com o circuito fechado, a chamada segue o fluxo normal de execução.
O breaker recebe sucessos, falhas, rejeições e timeouts e mantém contadores em janela móvel.
Se o volume atinge o mínimo e a porcentagem de erros excede o limiar, o circuito abre.
Aberto, o circuito curto-circuita todas as requisições: o comando não é executado e o fluxo segue para o fallback.
Passado algum tempo, a requisição seguinte é liberada para a dependência: é o estado meio aberto.
Se essa requisição falha, o circuito volta a aberto pela duração da janela de espera; se tem sucesso, ele passa a fechado.
04 · FALLBACK E AFINAÇÃO
O que responder quando a chamada falha
Qualquer que seja a causa (timeout, rejeição ou curto-circuito), a requisição sempre passa pela lógica de fallback antes de voltar ao usuário. Isso dá ao comando a chance de fazer algo diferente de falhar rápido, que lança uma exceção e faz a aplicação descartar carga até a dependência voltar a ficar saudável.
O post lista abordagens de fallback em ordem de impacto sobre a experiência do usuário: ler de um cache, mesmo que os dados fiquem defasados; enfileirar escritas para persistir quando a dependência voltar (consistência eventual); voltar a valores padrão quando as opções personalizadas não podem ser recuperadas; e devolver nulo ou lista vazia, que as interfaces podem ignorar.
A documentação pede que o fallback dê uma resposta genérica, sem dependência de rede, a partir de cache em memória ou de lógica estática, e chama de má prática um fallback que possa falhar. O post acrescenta que o próprio fallback é protegido por semáforo, para que uma implementação lenta com chamada de rede não derrube a aplicação inteira.
As abordagens de tolerância a falhas têm prós e contras, mas combinadas formam uma barreira de proteção abrangente entre as requisições dos usuários e as dependências. A tabela resume o papel de quatro delas segundo o post.
| Quando entra em ação | Efeito descrito | |
|---|---|---|
| Pool de threads por dependência | A dependência fica latente e satura o próprio pool | Threads de requisição expiram ou são rejeitadas na hora, sem bloquear |
| Semáforo | Execuções que não fazem chamada de rede | Evita o custo de uma thread separada |
| Circuit breaker | O comando passa de um limiar de erros | Rejeita todas as requisições até as verificações de saúde terem sucesso |
| Fallback | Timeout, rejeição ou curto-circuito | Dá a chance de fazer algo diferente de falhar rápido |
Os timeouts na camada do comando são a primeira linha de defesa, mas os timeouts de rede continuam importantes porque chamadas muito lentas poderiam encher o pool da dependência indefinidamente. No exemplo do post, o pool tem tamanho 10 para absorver uma rajada de requisições no percentil 99, mas com tudo saudável costuma ter só 1 ou 2 threads ativas, atendendo chamadas de mediana de cerca de 40 ms.
Como as configurações podem ser mudadas em tempo real, os ajustes não arriscam derrubar a aplicação inteira em caso de problema ou configuração errada.
05 · LEGADO
Hystrix em manutenção, ideias em vigor
O Hystrix nasceu do trabalho de engenharia de resiliência que a equipe da API do Netflix começou em 2011. Entre seus objetivos, segundo a documentação, estão impedir falhas em cascata em um sistema distribuído complexo e recorrer a fallback, degradando graciosamente quando possível.
Perguntas frequentes
O que é Netflix: circuit breakers e falha em cascata (Hystrix)?
Arquitetura real em produção. Pontos centrais: hystrix em manutenção desde 2018 e o padrão continua válido e o sucessor citado é o Resilience4j.
O que o design de Netflix: circuit breakers e falha em cascata (Hystrix) cobre?
Ele está organizado nestas seções: Dezenas de dependências e uma só delas lenta, Um compartimento por dependência, Parar de tentar o que provavelmente vai falhar, O que responder quando a chamada falha e Hystrix em manutenção, ideias em vigor.
Quanto tempo leva Netflix: circuit breakers e falha em cascata (Hystrix) e qual é o nível?
Leva cerca de 17 minutos para ler e tem nível Sênior.
Agora projete você mesmo
Rode este caso como uma entrevista na escola de System Design: você toma as decisões, o tutor as questiona e você vê a nota do seu desenho.