Retry pattern

Cada vez mais a web se torna uma plataforma de serviços ao passo que necessitamos estar conectados para que possamos fazer o uso destes. A grande maioria dos serviços hoje são hospedados na nuvem e a comunicação entre a aplicação e o serviço é extremamente crítica neste cenário. Os servidores que oferecem serviços de nuvem são servidores de alta disponibilidade e robustos o suficiente para minimizar ao máximo uma possível paralisação. Porém o caminho até um servidor pode não ser tão estável assim, visto que há outros recursos durante o percurso que podem impactar a utilização de um serviço, como infra interna da empresa e a operadora por exemplo.  Diante deste cenário, cada vez mais é exigido que as aplicações consigam lidar com esta instabilidade e garantam uma responsividade (capacidade de dar repostas) aceitável num cenário que pode ser imprevisível. 

 

Contexto 

Existem certos tipos de erros em uma aplicação que são momentâneos, são chamados erros Transientes. Estes erros, ou falhas, como ficar melhor, são devido a diversos fatores externos à aplicação e que muitas vezes não são previstos no desenvolvimento. Se um serviço torna-se indisponível por um certo tempo, a aplicação deveria saber tratar este problema. É comum fazer o acesso aos serviços dentro de um bloco Try catch para tratar uma possível falha. Porém, como já vimos, existem erros que são momentâneos, e nesse caso uma nova tentativa teria grande chance de obter sucesso. Simplesmente “empacotar” uma chamada de um serviço dentro de um bloco Try catch apenas impede o erro de “quebrar” a aplicação e uma abordagem mais reativa cairia bem nesses cenários. 

O padrão conhecido como Retry Pattern vem ao encontro dessa abordagem e permite que a aplicação converse com os serviços de uma forma mais responsiva num cenário de instabilidades.  

Para implementar esse padrão, deve-se levar em consideração a natureza da falha em questão, pois nem sempre tentar novamente é o procedimento apropriado naquele momento. Podem haver casos em que simplesmente abortar a operação é o mais indicado.  Para identificarmos qual será a abordagem a ser seguida, vamos considerar o seguinte: 

  • Falhas que indicam não ser transitórias: falhas na autenticação ou dados inconsistentes por exemplo, devem ser abortadas após uma tentativa malsucedida, pois reenviar a mesma solicitação provavelmente não implicará num retorno bem-sucedido. 
  • Falhas pouco comuns ou raras: em situações como esta, onde a falha ocorrida é incomum ou raramente acontece, tentar novamente é uma boa abordagem, pois é muito pouco provável que esta volte a ocorrer em uma nova tentativa, dada sua natureza rara. 
  • Falhas recorrentes (problemas de conexão ou ocupado): essas são falhas em que uma nova tentativa provavelmente resultará em um retorno bem-sucedido, isso porque esses tipos de problemas são instabilidades momentâneas e por serem de natureza esporádica, a chance de sucesso aumenta muito em uma nova tentativa. 

 

Use com parcimônia 

Se o problema por exemplo for relacionado a uma sobrecarga no servidor, fazer uma nova chamada pode agravar ainda mais a situação. Isso fará com que o servidor leve mais tempo para normalizar seu funcionamento. Então como lidar com essas situações? O Gmail implementa uma forma bem interessante de resolver isso, se você estiver off-line e tentar atualizar seus e-mails, suas tentativas terão intervalos cada vez maiores. Essa abordagem de intervalos maiores ou exponenciais permite que outras requisições sejam finalizadas antes de um nova tentativa, garantindo assim que o servidor não seja sobrecarregado por inúmeras requisições em um curto espaço de tempo. Não esqueçamos também de sermos cuidadosos com a quantidade de vezes que essa requisição será solicitada, até que atinja um limite. Limites altos, podem fazer com que a aplicação sobrecarregue ainda mais o servidor que ainda não restabeleceu o serviço, acarretando em um problema ainda maior. 

 

Modelo de implementação 

O modelo consiste em tentativas sucessivas de requisições em caso de falha. O diagrama abaixo mostra o fluxo das requisições. 

RetryPatternImage

 

Exemplo 

O exemplo abaixo faz o download do conteúdo de uma página e retorna a string correspondente à estrutura da página. Foram utilizadas as rotinas assíncronas do .NET (async e await), porém isso não se faz obrigatório. Existem diversas formas de implementar esse padrão, inclusive existem bibliotecas prontas para isso, um bom exemplo é a Polly.  

Obs.: Tratamentos na saída do resultado foram desconsiderados.

 

    class Program
    {
        private static int _maxTentativas = 3;
        private static readonly TimeSpan _delay = TimeSpan.FromSeconds(5);

        public static string DownloadContentFromWeb()
        {
            var client = new WebClient();
            return client.DownloadString("http://www.microsoft.com");
        }

        public static async Task<string> DownloadContentFromWebWithRetry()
        {
            int _tentativaAtual = 0;
            var client = new WebClient();
            var conteudo = string.Empty;

            for (; ; )
            {
                try
                {
                    conteudo = await client.DownloadStringTaskAsync("http://www.microsoft.com.br");
                    break;
                }
                catch (Exception ex)
                {
                    Trace.TraceError(ex.Message);
                    _tentativaAtual++;

                    if (_tentativaAtual > _maxTentativas)
                    {
                        throw;
                    }
                }
                await Task.Delay(_delay);
            }
            return conteudo;
        }

        static void Main(string[] args)
        {
            Console.WriteLine(DownloadContentFromWebWithRetry().Result);
            Console.ReadKey();
        }
    }
}

 

O código acima define alguns valores inciais como número máximo de tentativas e o tempo de espera entre cada tentativa. Todo o processamento ocorre dentro de uma estrutura de repetição for. Se o resultado chegar na primeira tentativa, o comando break interrompe o laço e retorna o valor, do contrário, este cairá na captura da exceção, incrementa a variável que controla a tentativa atual e em seguida verifica se o número máximo de tentativas foi atingido. Se ainda não foi, a task aguarda o delay para a próxima tentativa.  

É um processo bem simples que mostra uma forma resiliente de deixar uma aplicação mais responsiva na hora de lidar com possíveis falhas que possam vir a ocorrer. 

Fonte: Microsoft Docs 

 

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair /  Alterar )

Foto do Google

Você está comentando utilizando sua conta Google. Sair /  Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair /  Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair /  Alterar )

Conectando a %s

%d blogueiros gostam disto: