Vou tentar explicar neste artigo um dos principais padrões de projetos utilizados em arquitetura de microservices, o circuit breaker.
Ficou um pouco extenso o artigo, mas o objetivo é deixar bem claro como funciona o padrão, navegue pelo menu abaixo para facilitar o entendimento do artigo.
Navegue pelo conteúdo deste artigo
Definição
O “Circuit Breaker” é um padrão de projeto no qual você tenta uma chamada de serviço externo em um número configurável de vezes. Se a chamada de serviço falhar o número configurado de vezes seguidas, abre-se o circuito, isso significa que as chamadas sucessivas não vão mais chamar o serviço externo e sim retornar imediatamente. Isso continuará a acontecer durante o período de tempo configurado. Ele atua como uma espécie de “disjuntor” entre as diferentes partes do sistema, monitorando continuamente as solicitações e as respostas entre elas.
Estados do Circuit Breaker
O Circuit Breaker é composto por três estados:
- Closed (Fechado): Nesse estado, todas as solicitações são enviadas diretamente para o componente externo. O Circuit Breaker monitora continuamente as solicitações e as respostas. Se a taxa de falhas atingir um determinado limite, ele entrará no próximo estado.
- Open (Aberto): Nesse estado, todas as solicitações serão bloqueadas e a resposta do componente externo não será considerada. Em vez disso, o Circuit Breaker retornará um erro imediatamente. Após um período de tempo pré-definido, o Circuit Breaker tentará enviar uma solicitação de teste para o componente externo para verificar se ele está funcionando novamente. Se a resposta for bem-sucedida, ele entrará no próximo estado.
- Half-Open (Meio aberto): Nesse estado, o Circuit Breaker permite um número limitado de solicitações para o componente externo. Se todas as respostas forem bem-sucedidas, ele voltará para o estado “fechado”. Se houver uma taxa de falhas elevada, ele voltará para o estado “aberto”.
É um pouco estranho para entender mas é análogo a eletricidade pois ela só pode fluir se o circuito elétrico estiver fechado, caso o circuito elétrico se “abra” não existe a condução da eletricidade.
Porque ele é importante?
O pattern está ae para ser usado na prevenção de efetuar chamadas a operações que não respondem, uma vez que no cenário de aplicações distribuídas é comum o acesso de serviços externos temos que nos prevenir. Podemos ter problemas na ponta, ou seja o serviço desejado pode estar fora do ar, problemas de rede, timeouts, implementações incorretas que retornam status code 500.
O Circuit Breaker ajuda a melhorar a resiliência do sistema, impedindo que as falhas em um componente externo causem uma cascata de falhas em todo o sistema. Em vez disso, ele ajuda a detectar e isolar rapidamente as falhas, permitindo que o sistema se recupere mais rapidamente e minimize o impacto sobre os usuários.
Quando usar o Circuit Breaker?
Quando seu projeto precisar acessar um ou mais serviços externos.
Caso de uso
Para mostrar o Circuit Breaker na prática vamos pensar no seguinte cenário: Teremos dois microservices onde teremos informaçõe de “Filmes” e outro microservice onde teremo as “Avaliações” de um determinado filme escolhido.
Quando fizermos um “GET” para buscar um filme, internamente o microservice de filmes irá fazer uma chamada HTTP para o microservice de avaliações na intenção de retornar todas as avaliações de um determinado filme.
Obs: A implementação dos microservices deste exemplo foram feitas em Java utilizando o Spring Boot como framework, duas aplicações simples que possuem os dados mockados somente para exemplo e utilizei o IntelliJ na versão community.
Todo o código fonte utilizado aqui neste exemplo está disponivel aqui no meu Github.
Importante: no github o projeto possui duas branches, na branch main está o código funcional sem a implementação do circuit breaker e na branche “circuit-breaker” está a implementação do pattern. Acho que assim fica melhor o entendimento.
Imagem do código do microserviço de Filme (rodando em localhost:8080):

Imagem do código do microserviço de Avaliações (rodando em localhost:8081):

Exemplo de uma chamada para buscar as informações de um filme:

Muito bom, o cenário acima é o melhor possivel, temos os dois serviços em pé, o microservice de filmes acionando o microservice de avaliacoes via HTTP para buscar as informações das avaliações de um determinado filme.

Kkkk o Buzz fez uma pergunta para o Woody que pela cara dele com certeza ele não pensou ou não sabe responder. Nossa missão é ajuda-lo!
Na implementação sem o pattern Circuit Breaker vamos ter o seguinte problema:


Entendido o problema vamo agora fazer algumas alterações no código para implementar o pattern, para isso vamos utilizar o Resilience4j que é uma biblioteca de tolerância a falhas para o Java. Para maiores informações sugiro a leitura da documentação da biblioteca aqui.
Vamos somente alterar o microservice de filmes, o primeiro passo é incluir as dependências necessárias para implementar o pattern (essas dependências estão descritas na documentação):

O segundo passo é definir algumas configurações no application.properties do microservice, aqui no caso eu preferi trabalhar com yml, para isso basta renomear o arquivo de application.properties para application.yml e definir as seguintes configurações:

Essas configurações mais uma vez estão explicadas na documentação mas basicamente o parâmetro “minimumNumberOfCalls” irá determinar o número mínimo de chamadas que será necessária efetuar para o circuit breaker com base no parâmetro “slidingWindowSize” para que ele possa mudar de estado.
Neste exemplo vamos pedir para o circuit breaker memorizar as últimas 6 chamadas e quando fizer as 3 primeiras já vai calcular o estado do circuit breaker (por padrão deve ser 50%).
Vamos agora alterar o ponto onde o microervice de filmes aciona o microservice de avaliações:

Com isso vamos subir os dois projetos e testar novamente a requisição para ver se ainda continua trazendo as avaliações corretamente.

Agora vamos derrubar o microservice de avaliações e refazer a chamada, se tudo der “certo” deveremos receber erro 500, vamos analisar a imagem abaixo:

Muito bom isso, fiz no Postman 3 chamadas ao serviço e retornou 500 em todos, nos três primeiros o microserviço de filmes tentou se conectar com o microservice de avaliações, portanto demorou muito tempo para retornar o problema.
Na quarta tentativa o pattern já entendeu que o microservice de avaliações estava fora e “abriu o circuito“, note pela imagem que o erro mudou e o pattern está informando que o circuito está “OPEN” não permitindo mais chamadas ao microservice de avaliação e retornando o erro muito mais rápido.
Tá mais como contornar esse problema do erro quando o cicuito está aberto?
Vamos implementar um “fallback” ou método padrão que o circuit breaker irá executar caso o circuito abrir.
Vamos alterar algumas coisas na classe AvaliacaoClientImpl.java:

Com essas alterações podemos testar novamente a aplicação de filmes com o microservice de avaliações “desligado”:


Se agora efetuarmos a consulta do filme com id 2 por exemplo (este ainda não está em cache) vamos receber o seguinte resultado:


Considerações Finais
É isso por hoje! entendemos um dos padrões mais utilizados no mundo dos microservices, usar esse padrão é uma forma de atuar preventivamente em problemas de instabilidade, aumento de latência ou falha na camada de rede ou ainda indisponibilidade de um microserviço como foi no nosso exemplo.
Lógico que esse foi um exemplo bem simples, mas vimos como implementar uma biblioteca de resiliência, e o mais importante como implementar um recurso de fallback ou seja o que o sistema deve fazer em caso de algum problema, neste nosso caso pegamos algumas informações de um “cache” maroto em memória mas poderia ser outra ação, como enviar email para equipe de suporte informando que uma API está fora.
Até a proxima e tendo dúvidas podem me chamar ae!!