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!