Back-End

23 ago, 2016

Aprenda a aumentar a resiliência de suas aplicações Ruby

Publicidade

Atualmente, para desenvolver um software moderno, acaba-se fazendo a integração com diversos outros softwares. Através de APIs é conectada em banco de dados uma conta com mecanismos de Cache, entre outros tipos. De maneira geral, são adicionados diversos pontos onde um software pode vir a falhar e, provavelmente, aparecerá uma tela de erro 500 na tela.

Se você tem um software no qual cada clique pode ser seu dinheiro se perdendo, certamente procurará mantê-lo disponível pelo maior tempo possível. Porém, a qualquer momento, uma API do seu gateway de pagamento pode ficar indisponível, ou uma latência enorme da sua aplicação para o banco de dados. Mas, convenhamos, que não é nada agradável para a experiência do usuário aguardar o tempo de timeout (tempo máximo que uma aplicação aguarda alguma resposta de um serviço) e, no final de tudo, ainda ver um erro acontecendo em sua requisição. Podendo ainda piorar, imagine que a aplicação tem milhares de requisições por segundo e todos os usuários segurando as threads ou processos do serviço, pois todos estão aguardando um timeout por conta de um serviço instável. A aplicação cairá rapidamente e, se nesse momento o usuário estiver prestes a contratar seu serviço ou comprar um item, ele com certeza desistirá e você perderá a sua venda.

Entrando na hype de microsserviços, é bastante comum que um dos serviços possa falhar, e sabemos que não se pode deixar o usuário aguardando um erro. Pensando nisso, Martin Folwer escreveu um artigo em seu blog explicando um pouco mais sobre Circuit Breaker, uma técnica inspirada no grande livro Release It!, escrito por Michael Nygard, onde a técnica consiste basicamente em envolver as chamadas RPC (Remote Procedure Call) em uma classe/função que monitora as possíveis falhas. Quando a quantidade de falhas chegar a um certo limite, definido pelo desenvolvedor, o Circuit Breaker entrará em um estado chamado “aberto”, em que as próximas requisições serão levadas a um erro previamente conhecido. Outra parte importante é que você vai querer monitorar, caso o Circuit Breaker for ativado ou não. A imagem ilustra melhor o funcionamento de um Circuit Breaker Pattern.

Destaque_iMasters_08-2016 1

Utilizando Circuit Breaker

Criar um Circuit Breaker é bem simples – pode ser criada uma classe que recebe um bloco de execução e efetua dentro dela, literalmente envelopando o bloco de código, que controla os possíveis erros. Para isso, será utilizada uma implementação, já pronta, do Circuit Breaker Pattern. Como exemplo, criaremos uma classe que faz uma requisição na API do Github, que traz os dados públicos do usuário – esse é um caso em que, por exemplo, o serviço do GitHub poderia ficar indisponível ou sofrer alguma instabilidade prejudicando o desempenho da aplicação.

Por exemplo, vamos escrever código em Ruby e utilizar uma gem com a implementação já pronta. Caso tenha interesse em ver como criar o código do seu próprio Circuit Breaker, sugiro que visite o blog do Martin Fowler, o link está nas referências.

Importe em seu projeto Ruby a gem circuit_brakage, basta adicionar em seu Gemfile a seguinte linha:

gem 'circuit_breakage'

Agora, para criar uma classe que faz requisições de dados públicos de usuários na API do GitHub e na execução da chamada da API do GitHub, protegeremos as requisições com o nosso Circuit Breaker:

class Github
 GITHUB_BASE_URI = "https://api.github.com"
 def initialize
   @breaker = CircuitBreakage::Breaker.new
   @breaker.failure_threshold =   3
   @breaker.duration          =  10
   @breaker.timeout           =  10
 end
 
 def user(nickname)
   @breaker.call do
     response = RestClient.get "#{GITHUB_BASE_URI}/users/#{nickname}"
     JSON.parse response.body, symbolize_names: true
   end
 end
end

Como pode ser observado no código, na criação de um objeto do tipo GitHub, já foi instanciado o Circuit Breaker e atribuído a ele a uma variável @breaker para que possam ser configuradas suas particularidades e depois utilizadas.

Ainda seguindo os conceitos descritos por Martin Fowler, existem outras observações sobre o código. A primeira é o failure_threshold, que é a quantidade de erros que devem acontecer antes de abrir o circuito e não enviar as requisições ao servidor do GitHub – nesse caso, definimos apenas 3 e o circuito será aberto, lembrando que isso pode variar de acordo com a regra de negócio.

Outro ponto que deve ser mencionado é o duration, que significa quanto tempo, em segundos, que o circuito deve ficar aberto, ou seja, não envia as requisições. Após esse tempo, o Circuit Breaker deve tentar novamente que as requisições sejam enviadas.

Por último, e não menos importante, temos o timeout, que é o tempo que o Circuit breaker deve aguardar a resposta do servidor antes de lançar uma exception para que possa ser tratada.

Ao criar o objeto, já foram definidas as configurações do Circuit Breaker e suas particularidades, agora, basta usá-lo. Com isso, foi feito conforme Martin Fowler descreve em seu blog, está sendo executado um bloco de código com o possível erro dentro do Circuit Breaker, e qualquer problema de timeout está potencialmente protegido pelo código do Circuit Breaker.

Conforme o exemplo, foi criado um código protegido pelo Circuit Breaker Pattern, de modo que, ao atingir uma certa quantidade de erros, definidas por configuração, as requisições não são enviadas para a API do GitHub.

Nesse exemplo, é usado de maneira mais simples possível um Circuit Breaker Pattern, a fim de mostrar como pode ser evitado que o usuário fique aguardando para receber um erro.

Outra boa prática existente, inclusive sugerida pelo próprio Martin Fowler, é monitorar quando o circuito está aberto. Para isso, a gem dispara duas exceptions para facilitar, que são:

CircuitBreakage::CircuitOpen e  CircuitBreakage::Timeout

Para isso, podem ser tratadas as exceptions e enviadas métricas tanto para um LogStash ou StatsD para que métricas sejam tiradas futuramente.

begin
 @breaker.call do
   response = RestClient.get "#{GITHUB_BASE_URI}/users/#{nickname}"
   JSON.parse response.body, symbolize_names: true
 end
rescue CircuitBreakage::CircuitOpen
 Statsd.increment "github.users.circuit_open"
rescue CircuitBreakage::CircuitTimeout
 Statsd.increment "github.users.circuit_timeout"
end

Também pode-se utilizar das exceptions para tratar o comportamento da aplicação e realizar outras operações. Por exemplo, caso o GitHub fique indisponível, o usuário pode ser procurado no BitBucket ou fazer qualquer outra coisa que possa ter um feedback positivo para o usuário antes de ele ficar aguardando um timeout para ver a tela de erro.

Existem ainda várias outras implementações até mais sofisticadas do Circuit Breaker Pattern – uma bem famosa e bastante utilizada é a do pessoal da Shopify, o Semian, que já tem uma estrutura mais complexa, mas também é de simples utilização. Também existem para várias linguagens de programação.

Conclusão

Ao utilizar diversos serviços externos, deve-se preparar a aplicação caso algum serviço esteja indisponível; para isso, é utilizado o Pattern conhecido como Circuit Breaker proposto por Martin Fowler. Circuit Breaker Pattern é uma maneira de aumentar a resiliência de sua aplicação, a fim de se defender de possíveis falhas que podem ocorrer em sistemas externos.

Ao utilizar um Circuit Breaker Pattern, pense com cuidado em quais partes de sua aplicação você deseja colocar, e principalmente, como você irá configurá-lo para agir, para que te ajude e não atrapalhe, fazendo com que você perca mais do que ganhe.

Referências