Explorar repetições de chamadas HTTP personalizadas com retirada exponencialExplore custom HTTP call retries with exponential backoff

Para criar microsserviços resilientes, você precisa lidar com possíveis cenários de falha de HTTP.To create resilient microservices, you need to handle possible HTTP failure scenarios. Uma maneira de lidar com essas falhas, embora não seja recomendada, é criar sua própria implementação de repetições com retirada exponencial.One way of handling those failures, although not recommended, is to create your own implementation of retries with exponential backoff.

Observação importante: esta seção mostra como você pode criar seu próprio código personalizado para implementar repetições de chamadas HTTP.Important note: This section shows you how you could create your own custom code to implement HTTP call retries. No entanto, não é recomendado fazer isso sozinho, mas sim usar mecanismos mais avançados e confiáveis, embora mais simples de usar, como o HttpClientFactory com a Polly, disponível desde o .NET Core 2.1.However, it isn't recommended to do it on your own but to use more powerful and reliable while simpler to use mechanisms, such as HttpClientFactory with Polly, available since .NET Core 2.1. As abordagens recomendadas são explicadas nas próximas seções.Those recommended approaches are explained in the next sections.

Como uma exploração inicial, você poderia implementar seu próprio código com uma classe de utilitário para retirada exponencial como em RetryWithExponentialBackoff.cs, além de um código semelhante ao seguinte.As an initial exploration, you could implement your own code with a utility class for exponential backoff as in RetryWithExponentialBackoff.cs, plus code like the following.

public sealed class RetryWithExponentialBackoff
{
    private readonly int maxRetries, delayMilliseconds, maxDelayMilliseconds;

    public RetryWithExponentialBackoff(int maxRetries = 50,
        int delayMilliseconds = 200,
        int maxDelayMilliseconds = 2000)
    {
        this.maxRetries = maxRetries;
        this.delayMilliseconds = delayMilliseconds;
        this.maxDelayMilliseconds = maxDelayMilliseconds;
    }

    public async Task RunAsync(Func<Task> func)
    {
        ExponentialBackoff backoff = new ExponentialBackoff(this.maxRetries,
            this.delayMilliseconds,
            this.maxDelayMilliseconds);
        retry:
        try
        {
            await func();
        }
        catch (Exception ex) when (ex is TimeoutException ||
            ex is System.Net.Http.HttpRequestException)
        {
            Debug.WriteLine("Exception raised is: " +
                ex.GetType().ToString() +
                " –Message: " + ex.Message +
                " -- Inner Message: " +
                ex.InnerException.Message);
            await backoff.Delay();
            goto retry;
        }
    }
}

public struct ExponentialBackoff
{
    private readonly int m_maxRetries, m_delayMilliseconds, m_maxDelayMilliseconds;
    private int m_retries, m_pow;

    public ExponentialBackoff(int maxRetries, int delayMilliseconds,
        int maxDelayMilliseconds)
    {
        m_maxRetries = maxRetries;
        m_delayMilliseconds = delayMilliseconds;
        m_maxDelayMilliseconds = maxDelayMilliseconds;
        m_retries = 0;
        m_pow = 1;
    }

    public Task Delay()
    {
        if (m_retries == m_maxRetries)
        {
            throw new TimeoutException("Max retry attempts exceeded.");
        }
        ++m_retries;
        if (m_retries < 31)
        {
            m_pow = m_pow << 1; // m_pow = Pow(2, m_retries - 1)
        }
        int delay = Math.Min(m_delayMilliseconds * (m_pow - 1) / 2,
            m_maxDelayMilliseconds);
        return Task.Delay(delay);
    }
}

Usar esse código em um aplicativo C# cliente (outro microsserviço de cliente da API Web, um aplicativo MVC ASP.NET ou até mesmo um aplicativo Xamarin C#) é simples.Using this code in a client C# application (another Web API client microservice, an ASP.NET MVC application, or even a C# Xamarin application) is straightforward. O exemplo a seguir mostra como fazer isso, usando a classe HttpClient.The following example shows how, using the HttpClient class.

public async Task<Catalog> GetCatalogItems(int page,int take, int? brand, int? type)
{
    _apiClient = new HttpClient();
    var itemsQs = $"items?pageIndex={page}&pageSize={take}";
    var filterQs = "";
    var catalogUrl = $"{_remoteServiceBaseUrl}items{filterQs}?pageIndex={page}&pageSize={take}";
    var dataString = "";
    //
    // Using HttpClient with Retry and Exponential Backoff
    //
    var retry = new RetryWithExponentialBackoff();
    await retry.RunAsync(async () =>
    {
        // work with HttpClient call
        dataString = await _apiClient.GetStringAsync(catalogUrl);
    });
    return JsonConvert.DeserializeObject<Catalog>(dataString);
}

Lembre-se de que esse código é adequado apenas para prova de conceito.Remember that this code is suitable only as a proof of concept. As próximas seções explicam como usar abordagens mais sofisticadas, embora mais simples, usando o HttpClientFactory.The next sections explain how to use more sophisticated approaches while simpler, by using HttpClientFactory. O HttpClientFactory está disponível desde o .NET Core 2.1, com bibliotecas de resiliência comprovadas, como a Polly.HttpClientFactory is available since .NET Core 2.1, with proven resiliency libraries like Polly.