Hands-on: aplicando o Circuit Breaker na prática
por Michael Douglas Barbosa Araujo, Subject Matter Expert na Comunidade de Soluções de Investimentos no Itaú
No último artigo, abordamos toda a teoria por trás da ferramenta chamada “Circuit Breaker”, uma potente solução para a área de microsserviços utilizada no aplicativo de investimentos do Itaú — o íon. Essa solução, capaz de nos ajuda a lidar com possíveis falhas de serviços dependentes, se trata de um padrão de projeto utilizado em arquitetura de softwares que podemos encontrar em diversas aplicações, o que inclui também algumas soluções internas que utilizamos no banco.
Já conferiu a primeira parte do artigo e absorveu toda a teoria sobre o tópico? Então agora vamos entender como ele se aplica na prática.
Vamos ao código!
E como diria Linus Torvalds, “vamos ao código”! Ou seja, agora é o momento de vermos na prática o que falamos até o momento.
Um adendo antes de colocarmos a mão na massa: em nosso exemplo criaremos um Circuito Fechado utilizando Python com Pybreaker. No entanto, saiba que que existem outras bibliotecas muito boas e que o nosso exemplo poderia ser facilmente criado em qualquer uma delas. Seguem alguns exemplos:
circuit-breaker (Python)
pybreaker (Python)
Hystrix (Java)
Polly (.NET)
Opossum (Node.js)
Dito isso, bora praticar? Para iniciar, crie uma pasta em seu ambiente com o nome de sua preferência. Depois, abra o terminal nesta pasta e então execute o comando a seguir para criar a venv:
python3 -m venv venv
Ah, lembrando que os comandos aqui exemplificados foram executados em uma máquina Windows. Caso tenha um sistema operacional diferente, verifique como fazer o mesmo com o seu sistema. Agora vamos ativar o ambiente executando o comando:
source venv/Scripts/activate
Abra uma IDE ou editor de códigos de sua preferência. Para o nosso tutorial irei utilizar o Visual Studio Code. Sendo assim, irei executar na raiz do projeto o comando:
source venv/Scripts/activate
Após abrir o VSCode, volte ao terminal para instalarmos o Flask e requests. Para isso, execute no terminal:
pip install Flask
pip install requests
Agora, crie uma pasta chamada api dentro da pasta que acabou de criar, e então crie um script chamado app.py — esse script será responsável por criar uma pequena api que estará encarregada em devolver casos de sucesso, falha e randomizar entre os status. Lembre-se de deixar a estrutura da seguinte maneira:
import random
import time
from flask import Flask
app = Flask(__name__)
@app.route('/success')
def success_endpoint():
return {
"message": "Chamada realizada com sucesso."
}, 200
@app.route('/failure')
def faulty_endpoint():
r = random.randint(0, 1)
if r == 0:
time.sleep(2)
return {
"message": "Falhou."
}, 500
@app.route('/random')
def fail_randomly_endpoint():
r = random.randint(0, 1)
if r == 0:
return {
"message": "Chamada realizada com sucesso."
}, 200
return {
"message": "Falhou (algumas vezes)."
}, 500
Após a criação, execute o comando a seguir na pasta api para subir o Flask:
flask run
E caso tudo esteja tudo correto, você terá como retorno:
Running on http://127.0.0.1:5000
Agora, criaremos dois tipos de teste, no qual no primeiro faremos um loop para verificar o estado do circuito ao chamar a nossa api. Você irá verificar que em nosso código existe uma condição para, em alguns momentos, chamar o endpoint de falha, isso porque ele tem uma regra de timeout e o Random. Esse teste é bem bacana, porque nos possibilitar passar por quase todos os estados possíveis.
Sendo assim, crie na raiz do projeto o script testLoop.py e deixe-o com a seguinte estrutura:
import requests
from pybreaker import CircuitBreaker, CircuitBreakerError
from colorama import Fore, Style
# Configurações do circuit breaker
failure_threshold = 3 # Número máximo de falhas consecutivas permitidas
success_threshold = 5 # Número mínimo de sucessos consecutivos necessários
# Tempo de espera em segundos antes de tentar novamente após o circuit breaker bloquear a chamada
reset_timeout = 3
# Cria o circuit breaker
breaker = CircuitBreaker(fail_max=failure_threshold,
reset_timeout=reset_timeout)
@breaker
def call_endpoint(value):
# Se for par, chame o endpoint /failure
# Se for ímpar, chame o endpoint /random
endpoint = '/failure' if value % 2 == 0 else '/random'
response = requests.get(f'http://localhost:5000{endpoint}')
# Lança uma exceção caso a resposta não seja bem-sucedida
response.raise_for_status()
return response.json()
# Executa o loop para testar o circuit breaker
for i in range(1, 11):
print(f"Executando iteração {i}")
try:
result = call_endpoint(i)
print(Fore.GREEN + 'Chamada bem-sucedida!')
print(Fore.GREEN + f"Resultado: {result}")
print(Style.RESET_ALL)
except CircuitBreakerError:
print(Fore.RED + 'Chamada bloqueada pelo circuit breaker')
print(Style.RESET_ALL)
except requests.exceptions.RequestException as e:
print(Fore.RED + f'Erro ao chamar o endpoint: {e}')
print(Style.RESET_ALL)
print("---------------------------")
print("Loop concluído!")
E para fazer o nosso teste no terminal, execute:
python3 testLoop.py
Cada execução terá um retorno. Em meu caso, obtive como resposta:
Executando iteração 1
Chamada bem-sucedida!
Resultado: {'message': 'Chamada realizada com sucesso.'}
---------------------------
Executando iteração 2
Erro ao chamar o endpoint: 500 Server Error: INTERNAL SERVER ERROR for url: http://localhost:5000/failure
---------------------------
Executando iteração 3
Chamada bem-sucedida!
Resultado: {'message': 'Chamada realizada com sucesso.'}
---------------------------
Executando iteração 4
Erro ao chamar o endpoint: 500 Server Error: INTERNAL SERVER ERROR for url: http://localhost:5000/failure
---------------------------
Executando iteração 5
Chamada bem-sucedida!
Resultado: {'message': 'Chamada realizada com sucesso.'}
---------------------------
Executando iteração 6
Erro ao chamar o endpoint: 500 Server Error: INTERNAL SERVER ERROR for url: http://localhost:5000/failure
---------------------------
Executando iteração 7
Erro ao chamar o endpoint: 500 Server Error: INTERNAL SERVER ERROR for url: http://localhost:5000/random
---------------------------
Executando iteração 8
Chamada bloqueada pelo circuit breaker
---------------------------
Executando iteração 9
Chamada bloqueada pelo circuit breaker
---------------------------
Executando iteração 10
Chamada bloqueada pelo circuit breaker
---------------------------
Loop concluído!
Para fecharmos o nosso teste, vamos criar um script Flask, que será um serviço consumidor da nossa api onde a função dele é consumir a api. Mas não se esqueça de verificar se ela está funcionando normalmente com o padrão Circuit Breaker.
Para isso criaremos um script novo na raiz, chamado testCall.py, e deixaremos com a seguinte estrutura de código:
from flask import Flask, jsonify
from pybreaker import CircuitBreaker, CircuitBreakerError
import requests
app = Flask(__name__)
# Configurações do circuit breaker
failure_threshold = 3 # Número máximo de falhas consecutivas permitidas
success_threshold = 5 # Número mínimo de sucessos consecutivos necessários
# Tempo de espera em segundos antes de tentar novamente após o circuit breaker bloquear a chamada
reset_timeout = 10
# Cria o circuit breaker
breaker = CircuitBreaker(fail_max=failure_threshold,
reset_timeout=reset_timeout)
@breaker
def check_service_status():
response = requests.get('http://127.0.0.1:5000/random')
if response.status_code == 200:
return True
else:
raise Exception('O serviço está fora do ar!')
@app.route('/status')
def status():
try:
check_service_status()
return jsonify({'status': 'Serviço está funcionando corretamente!'})
except CircuitBreakerError:
return jsonify({'status': 'Serviço está com problemas!'})
except Exception as e:
return jsonify({'status': str(e)})
@app.route('/service')
def service():
return jsonify({'data': 'Dados do serviço'})
if __name__ == '__main__':
app.run(port=8080)
E agora, para realizar o teste, vamos subir o nosso serviço. Para isso execute o comando na raiz do projeto:
flask --app testeCall run --port=8080
Caso tudo esteja correto você terá como retorno:
* Serving Flask app “testeCall”
* Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on http://127.0.0.1:8080
Press CTRL+C to quit
Por fim, podemos realizar o teste ao chamar a url: 127.0.0.1:8080/status. Repare que em alguns momentos ele informa que está tudo correto, e em outros que não. Isso porque estamos chamando o endpoint randômico que faz com que se tenha esse estado.
Como demonstrado, o Circuit Breaker pode ser uma ferramenta poderosa para garantir a resiliência em ambientes baseados em microsserviços. Monitorando a comunicação entre os serviços e interrompendo a comunicação em um serviço que apresenta falhas, neste artigo, você pôde ter um passo-a-passo de sua aplicação e validação.
Já utilizou o Circuit Breaker ou aplica a ferramenta no seu dia a dia? Compartilhe com a gente as suas percepções nos comentários! Tem alguma dúvida sobre o conteúdo ou gostaria de ler sobre algum outro tema por aqui? Pode dividir com a gente também para continuarmos a nossa conversa. ;)