Retry Logic para erros transientes no Windows Azure SQL Database (SQL Azure) - Parte 3

Parte 3 Adicionando o Retry Logic

Autor: Marcelo Franceschi de Bianchi - CTS LATAM

Revisor: Roberto Cavalcanti - CTS LATAM

Dando continuidade à série de três artigos relacionados ao Retry Logic, esse é o de número 3 no qual você aprenderá como Adicionar o código Retry Logic na sua aplicação que fará a conexão com o banco de dados Windows Azure SQL Database (SQL Azure).

1. Adicionado o Retry Logic

Caso tenha pulado o artigo dois você poderá realizar o download dos arquivos tutorial files clip_image001 que são referentes à solução do Visual Studio com o nome de RetryLogicTutorial_Queries.sln.

1.1 Referência a CAT retry library:

A primeira coisa a ser feita será realizar a referência a bibliteca do Retry Logic CAT retry logic library:

  1. Faça o download da biblioteca https://appfabriccat.com/2011/02/transient-fault-handling-framework/ clip_image001[1]
  2. Faça um build da biblioteca
  3. Na aplicação vá até as propriedades do projeto e ajuste Target Framework para que use o .Net Framework 4
  4. Adicione uma referência para Microsoft.AppFabricCAT.Samples.Azure.TransientFaultHandling.dll

O próximo passo será os using statements para o Form.cs

 using Microsoft.AppFabricCAT.Samples.Azure.TransientFaultHandling;
using Microsoft.AppFabricCAT.Samples.Azure.TransientFaultHandling.SqlAzure;

Adicione uma nova classe com o nome de MyRetryStrategy que implementa a interface ITransientErrorDetectionStrategy. Essa interface tem um metodo único, IsTransient, no qual irá capturar um objeto Exception e retornará true se a excessão representar um erro transiente.

 using System;
 using System.Data.SqlClient;
 using Microsoft.AppFabricCAT.Samples.Azure.TransientFaultHandling;
 using Microsoft.AppFabricCAT.Samples.Azure.TransientFaultHandling.SqlAzure;
  
 namespace RetryLogicTutorial
 {
     class MyRetryStrategy : ITransientErrorDetectionStrategy 
     {
         public bool IsTransient(Exception ex)
         {
             if (ex != null && ex is SqlException)
             {
                 foreach (SqlError error in (ex as SqlException).Errors)
                 { 
                     switch (error.Number)
                     {
                         case 1205:
                             System.Diagnostics.Debug.WriteLine("SQL Error: Deadlock condition. Retrying...");
                             return true;
  
                         case -2:
                             System.Diagnostics.Debug.WriteLine("SQL Error: Timeout expired. Retrying...");
                             return true;
                     }
                 }
             }
  
             // For all others, do not retry.
             return false;
         }
     }
 }

A implementação mostra um padrão tipico, primeiro o filtro para excessões do tipo SqlException. Veja o numero do erro em SqlException.Errors collection. Então retorne true para qualquer error que poderia disparar uma tentativa de retry e retorne false para todos os outros tipos de erros.

 void AdoQueryWorker_DoWork(object sender, DoWorkEventArgs e)
 {
     RetryPolicy retry = new RetryPolicy<MyRetryStrategy>(5, new TimeSpan(0, 0, 5));
  
     try
     {
         using (SqlConnection connection = new SqlConnection(builder.ConnectionString))
         {
             connection.OpenWithRetry(retry);
  
             SqlCommand command = new SqlCommand("select product_name from products");
             command.Connection = connection;
             command.CommandTimeout = CommandTimeout;
  
             SqlDataReader reader = command..ExecuteReaderWithRetry(retry);
  
             while (reader.Read())
             {
                 (sender as BackgroundWorker).ReportProgress(0, reader["product_name"]);
             }
         }
     }
     catch (SqlException ex)
     {
         MessageBox.Show(ex.Message, "SqlException");
     }
 }

A classe RetryPolicy<T> implementa o retry logic. O parametro T deverá ser um type que é implementado por ItransientErrorDetectionStrategy. No construtor RetryPolicy você deverá colocar o numero maximo de tentativas de execução e opcionalmente, você poderá também colocar o intervalo entre essas tentativas ou então utilizar um o exponential backoff.

O método OpenWithRetry é um método extendido, está defindo na biblioteca CAT, que adiciona o retry logic ao padrão ADO.NET ao método SqlConnection.Open. Se a excessão acontece, enquanto estiver abrindo a conexão, o objeto RetryPolicy aguarda por um intervalo e então faz a tentativa de execução, até que atinja o número máximo de tentativas que foi configurado.

Similarmente, ExecuteReaderWithRetry adiciona o retry logic para o método SqlCommand.ExecuteReader. Uma extensão dos métodos também é realizada por ExecuteNonQuery, ExecuteScalar e ExecuteXmlReader.

1.2 Adicionando Retry Logic do LINQ para SQL

A chamada da ADO.NET é realizada de forma bem direta. Mas em algumas aplicação que usam o framework tal como WCF ou então LINQ to SQL, no qual realizam chamadas abstratas ao banco de dados. Para esse tipo específico de cenário, a biblioteca CAT retry providência uma forma de empacotar um bloco de código dentro de um escopo que poderá ser feito a reexecução desse trecho de código. Para ver como isso funciona, modifique a função LinqQueryWorker_DoWork como segue:

 void LinqQueryWorker_DoWork(object sender, DoWorkEventArgs e)
 {
     RetryPolicy retry = new RetryPolicy<MyRetryStrategy>(5, TimeSpan.FromSeconds(5));
  
     try
     {
         e.Result = retry.ExecuteAction(() =>
             {
                 Deadlock(); // Artificially create a deadlock condition
  
                 CustomerOrdersDataContext ctx = new CustomerOrdersDataContext();
                 ctx.Connection.ConnectionString = builder.ConnectionString;
                 ctx.CommandTimeout = 3;
  
                 var results = from c in ctx.customers
                                 from o in c.orders
                                 from i in o.order_items
                                 select new { c.lname, c.fname, i.product.product_name, i.quantity };
  
                 return results.ToList();
             });
     }
     catch (SqlException ex)
     {
         MessageBox.Show(ex.Message, "SqlException");
     }
 }

O método RetryPolicy.ExecuteAction captura a expressão lambda. O código da expressão lambda é executado pelo menos uma vez. Se um erro transiente acontecer, o objeto RetryPolicy tentará realizar a execução do bloco de código novamente.

1.3 Utilize MyRetryStrategy

A implementação de MyRetryStrategy que foi mostrada nesse artigo é unicamente para demonstrar a retry API. A seguir temos uma lista dos principais erros transientes que você poderá utilizar na sua implmentação do código de Retry Logic:

Número do Erro

Descrição do Erro

20

The instance of SQL Server does not support encryption.

64

An error occurred during login.

233

Connection initialization error.

10053

A transport-level error occurred when receiving results from the server.

10054

A transport-level error occurred when sending the request to the server.

10060

Network or instance-specific error.

40143

Connection could not be initialized.

40197

The service encountered an error processing your request.

40501

The server is busy.

40613

The database is currently unavailable.

A biblioteca CAT retry possui uma classe denominada SqlAzureTransientErrorDetectionStrategy que você poderá utilizar, podendo ser um ótimo ponto de partida para a sua implementação da interface ItransientErrorDetectionStrategy.

2. Referências

Retry Logic para erros transientes no SQL Azure parte 1

Retry Logic para erros transientes no SQL Azure parte 2

Retry Logic for Transient Failures in SQL Azure

https://social.technet.microsoft.com/wiki/contents/articles/4235.retry-logic-for-transient-failures-in-sql-azure.aspx

Download the c sharp class directly the library from https://appfabriccat.com/2011/02/transient-fault-handling-framework/

SQL Azure Retry Logic Sample

https://code.msdn.microsoft.com/windowsazure/SQL-Azure-Retry-Logic-2d0a8401

SQL Azure Connection Retry (CODE WITH PARAMETERS)

https://blogs.msdn.com/b/bartr/archive/2010/06/18/sql-azure-connection-retry.aspx

SQL Azure Connectivity Troubleshooting Guide

https://social.technet.microsoft.com/wiki/contents/articles/sql-azure-connectivity-troubleshooting-guide.aspx