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 http://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

http://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 http://appfabriccat.com/2011/02/transient-fault-handling-framework/

SQL Azure Retry Logic Sample

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

SQL Azure Connection Retry (CODE WITH PARAMETERS)

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

SQL Azure Connectivity Troubleshooting Guide

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