Criando Aplicações Resilientes com Circuit Breaker

No post anterior, apresentamos o padrão de resiliência Retry. Mas o que acontece se o servidor de destino está falhando devido a uma sobrecarga? Fazer novas tentativas sobrecarregará ainda mais o servidor. É neste cenário que o padrão Circuit Breaker pode nos ajudar.

Em circuitos elétricos um circuit breaker (disjuntor) é um dispositivo que protege uma instalação elétrica de sobrecargas de corrente bem como curtos circuitos. Em sistemas distribuídos, a implementação do padrão circuit breaker ajudará a proteger o serviço de destino.

Esse padrão é mais complexo que o Retry pois se baseia numa maquina de estado como mostra a figura a seguir:

referência: Polly

Estados:

  • Closed/Fechado: em circuitos elétricos, quando um disjuntor está fechado indica que está passando corrente. No padrão de resiliência podemos dizer que está passando “requisições”;
  • Open/Aberto: neste estado, o circuito não passa as requisições ao destino, apenas retorna ao requisitante que o serviço está fora, evitando assim de sobrecarregar o destino com novas requisições, além de fornecer mais rapidamente um feedback, sem precisar aguardar o termino de uma requisição que provavelmente falhará. O circuito vai para o estado aberto após atingir um limite pré definido de falhas e permanecerá aberto também por um tempo definido; e
  • Meio aberto/Half-Open: esse é um estado de checagem, após esgotar o tempo no estado Aberto, o sistema vai para esse estado e a próxima requisição será encaminhada ao destino. Se o destino retornar falha, o circuito volta ao estado Aberto, se retornar com sucesso vai para o estado Fechado.

A animação a seguir ilustra o circuit breaker em ação com um limite (threshold) de duas falhas consecutivas para fazer a transição para o estado de Aberto:


Circuit Breaker com Polly

Novamente o Polly vem para facilitar a vida dos desenvolvedores na implementação do padrão circuit breaker em seus códigos, vejam como é simples:

No código, estamos configurando uma política de circuit breaker para quando houverem duas exceções (falhas) HttpRequestException consecutivas, o circuito pare de enviar requisições ao destino por 1 minuto. Durante esse período o circuito retornar uma BrokenCircuitException.

Observe no código que a política precisa ser a mesma instância entre as chamadas. Isso porque é necessário manter a instância da maquina de estado.

Vamos ver o código de um cliente consumindo esse serviço:

Temos a seguinte saída para o método Test_5_Calls:

Vejam que após a segunda chamada consecutiva com falha (HttpRequestException), o serviço passa a falhar rápido retornando umaBrokenCircuitException.


Configuração Avançada do Circuit Breaker

Imagine o cenário onde o serviço de destino está sendo balanceado e um dos servidores está sobrecarregado. Neste cenário, é provável que não teremos uma quantidade seguida de falhas para ativar o Circuit Breaker.

O framework Polly disponibiliza a política AdvancedCircuitBreaker que permitirá criar uma configuração mais robusta, na qual analisará uma amostragem de requisições durante um período e abrirá o circuito caso a quantidade de falhas atinja um certo percentual. Vale a pena explorar um pouco mais essa política.


Conclusão

A política de circuit breaker é muito importante para proteger o servidor de destino, bem como favorecer a experiência do usuário com uma falha rápida. A implementação dessa política com o Polly é relativamente simples.

Espero que tenham gostado, deixem seus feedbacks e continuem acompanhando.

Criando Aplicações Resilientes com o padrão Retry

(Versão em Inglês)

No artigo anterior, vimos que algumas das falhas possíveis em nossa aplicação são as falhas transientes. Estas falhas são automaticamente corrigidas após um curto período de tempo.

O padrão (ou política) Retry torna nossa aplicação resiliente ao fazer novas tentativas (automaticamenteem caso de falha.

A animação abaixo mostra o princípio de funcionamento do Retry. Neste cenário configuramos o mecanismo para fazer até 5 novas tentativas em caso de falha (se você preferir um gif, clique aqui):

Um ponto importante neste exemplo é que ao tornar nossa aplicação resiliente, o usuário final talvez não percebeu que houve falhas nas tentativas de comunicação. Ele fez uma requisição e teve seu resultado. Com isso, podemos dizer que melhoramos a experiência do usuário.

Algumas outras configurações possíveis desse padrão são:

  • RetryForever: fazer tentativas (eternamente) até que se tenha sucesso;
  • Retry(N): fazer N tentativas, e só após às N tentativas a falha é apresentada; e
  • WaitAndRetry(N, T): fará até no máximo N tentativas e esperará por T tempo entre cada tentativas (essa foi a forma utilizada na animação);

Retry Exponencial (Backoff)

Na configuração de Wait and Retry, uma das formas usais de configurar os intervalos entre tentativas é o chamado Retry Exponencial, na qual o intervalo cresce de forma exponencial.

Se você é usuário do Gmail®, por exemplo, já deve ter percebido essa situação. Veja na animação (acelerada) abaixo das tentativas de reconexão realizadas pelo Gmail quando ficamos sem conexão com a internet:

Ao plotar os intervalos entre as tentativas, fica claro que se trata de uma distribuição exponencial:

Distribuição Exponencial dos intervalos entre tentativas do Gmail

Implementando o Padrão Retry

Se você é desenvolvedor já deve estar imaginando como implementar esse padrão usando algumas estruturas como condicionais, laços de repetição, tratamentos de erros…

Não será necessário quebrar muito a cabeça, pois existem frameworks prontos e que irão facilitar muito nossa vida. Entre eles está o Polly Framework para aplicações .NET.

Polly é uma biblioteca que implementa uma gama de padrões de resiliência e tolerância a falhas para .Net e faz parte da .Net Foundation.

Outras features importantes, dessa biblioteca é a linguagem fluente de escrita de padrões de resiliência através de políticas, facilitar a reusabilidade, tratamentos nativos para Thread Safe e suporte Async. Muito Bom!!!

Implementando Retry com Polly

Veja como é simples:

Analisando:

  • Policy indica que iniciaremos uma nova política de resiliência;
  • A função Handle indica que a politica só será ativada se ocorrer a exceção SomeException;
  • A função WaitAndRetry indica a política que queremos e o argumento 5 indica que fará até 5 tentativas com um intervalo exponencial; e
  • colocamos na função Execute o código que será executado pela política, seja ele um procedimento (void) ou função.

Observe que a Polly tornou nossa política de retry explícita e legível, sem que precisássemos interpretar os codicionais e laços de repetição, etc.

A biblioteca Polly permite que você crie as políticas de diversas formas e configurações. O código acima é simple e usual para vários cenários.

O site oficial da biblioteca Polly é bem rico em conteúdo e pode ser melhor explorado.

Espero que tenham gostado e esquecem de deixar seus feedbacks.

No próximo post falaremos sobre o Circuit Breaker, até lá!!

Criando Aplicações Resilientes : Introdução

(Versão em Inglês )

O que é Resiliência? Se procurarmos por uma definição no dicionário (Google) veremos:

Na física: propriedade que alguns corpos apresentam de retornar à forma original após terem sido submetidos a uma deformação elástica.
No sentido figurado: é capacidade de se recobrar facilmente ou se adaptar à má sorte ou às mudanças.

Nesta série de posts iremos tratar de resiliência como:

a capacidade da aplicação de se recuperar de falhas e continuar a funcionar

Dizemos que:

  • No melhor cenário: a aplicação se recupera sem o usuário/cliente perceber
  • No pior cenário: a aplicação oferece serviços de forma limitada (que chamamos de graceful degradation)

Falhas

As falhas em sistemas distribuídos podem ser classificadas de acordo com sua duração em:

  • Transientes: Ocorrem uma vez e desaparecem com curta duração. Se você repetir a operação é provável que terá sucesso;
  • Intermitentes: São falhas transientes que ocorrem repetidamente, elas acontecem e desaparecem por “vontade própria”; e
  • Permanentes: São as falhas que permanecerão até que alguma ação seja tomada como, por exemplo, trocar um equipamento ou corrigir o software;

Também podemos classificar essas falhas segundo seu comportamento:

  • Travamento ou queda: o recurso parou de funcionar devido a um travamento ou perda do estado interno;
  • Omissão: o recurso não responde às requisições;
  • Temporização: as respostas estão fora da sincronia esperada; e
  • Bizantinas ou Aleatórias: o recurso responde forma totalmente arbitrária;

Por vezes quando projetamos e desenvolvemos nossos sistemas não levamos em consideração que eles poderão falhar. Peter Deutsch e James Gosling elencaram oito itens que normalmente são assumidos como verdadeiros em projetos de sistemas distribuídos e que a longo prazo se mostram errados e podem resultar em problemas (8 falácias de sistemas distribuídos), são eles:

  • A rede é de confiança. 
  • A latência é zero. 
  • Largura de banda é infinita.
  • A rede é segura. 
  • A topologia não muda. 
  • Há somente um administrador. 
  • Custo de transporte é zero. 
  • A rede é homogêneo.

Microsserviços

Está cada vez mais comum a construção de aplicações baseadas em arquitetura microsserviços ou a migração de monolíticos para essa arquitetura. A arquitetura de microsserviços tem promessas interessantes e entre elas é favorecer a alta disponibilidade

Porém, uma arquitetura de microsserviços com erros de modelagem em que existirem muitas chamadas HTTP encadeadas sincronamente, pode levar a um cenário oposto.

Vejam a imagem e os cenários a seguir, sendo simplista mas com o propósito de demostrar essa questão. 

  • Monolítico: um servidor tem uma disponibilidade calculada em 99.5% para servir uma sistema monolítico. 
  • Microsserviços: 5 servidores, também com 99.5% de disponibilidade cada. Porém, nessa arquitetura um serviço faz chamadas encadeadas e síncronas a outros serviços, logo, acrescenta outros pontos de falhas e a disponibilidade do sistema se dá pelo fator da disponibilidade de todos os serviços envolvidos. Neste cenário será 97.5%, sendo menor que a disponibilidade do cenário Monolítico. 

Em microsserviços as falhas podem ser diversas, desde falhas em hardware à movimentações de contêineres entre nós.

A questão que alguns podem ter é mesmo com uma disponibilidade alta como as dos nossos cenários, as falhas realmente podem acontecer? Devo me preocupar com isso?

Lei de Murphy: Se algo pode dar errado, vai dar errado!

Logo, não é uma questão de “se as falhas vão acontecer” e sim “quando elas vão acontecer”.

No livro “.NET Microservices: Architecture for Containerized .NET Applications” diz:

A falha intermitente é garantida em um sistema distribuído e baseado em nuvem, mesmo quando cada dependência têm uma disponibilidade excelente. Esse um fato que você precisa considerar.

Concluindo, ser Resiliente é aceitar que falhas irão acontecer e tratá-las da melhor maneira.

Nos próximos artigos veremos padrões aplicáveis à software para construir aplicações resilientes. 

Deixe seu feedback e acompanhe a série…

Bem Vindo!

Olá, meu nome é Maicon, sou formado em Ciência da Computação e apaixonado por desenvolvimento de software.

Nesta página irei compartilhar com vocês informações e experiências sobre o mundo desenvolvimento de software. De metodologias à coisas extremamente técnicas.

Espero que gostem!