Capítulo 1: Introdução ao Retentativas em Go

1.1 Compreendendo a Necessidade de Mecanismos de Retentativa

Em muitos cenários de computação, especialmente ao lidar com sistemas distribuídos ou comunicação em rede, as operações podem falhar devido a erros transitórios. Esses erros frequentemente são problemas temporários, como instabilidade de rede,indisponibilidade temporária de um serviço ou time-outs. Em vez de falhar imediatamente, os sistemas devem ser projetados para tentar novamente as operações que encontram esses erros transitórios. Essa abordagem melhora a confiabilidade e a resiliência.

Mecanismos de retentativa podem ser cruciais em aplicações em que a consistência e a completude das operações são necessárias. Eles também podem reduzir a taxa de erro que os usuários finais experimentam. No entanto, implementar um mecanismo de retentativa traz desafios, como decidir com que frequência e por quanto tempo tentar novamente antes de desistir. Aí é onde as estratégias de espera assumem um papel significativo.

1.2 Visão geral da Biblioteca go-retry

A biblioteca go-retry em Go fornece uma maneira flexível de adicionar lógica de retentativa às suas aplicações com várias estratégias de espera. Os principais recursos incluem:

  • Extensibilidade: Assim como o pacote http do Go, o go-retry é projetado para ser extensível com middleware. Você pode até escrever suas próprias funções de espera ou usar os filtros úteis fornecidos.
  • Independência: A biblioteca depende apenas da biblioteca padrão do Go e evita dependências externas, mantendo seu projeto leve.
  • Concorrência: É seguro para uso concorrente e pode funcionar com goroutines sem complicações adicionais.
  • Consciência de contexto: Ele suporta contextos nativos do Go para tempo limite e cancelamento, integrando-se perfeitamente ao modelo de concorrência do Go.

Capítulo 2: Importação de Bibliotecas

Antes de poder usar a biblioteca go-retry, ela precisa ser importada para o seu projeto. Isso pode ser feito usando o go get, que é o comando Go para adicionar dependências ao seu módulo. Basta abrir o terminal e executar:

go get github.com/sethvargo/go-retry

Este comando irá buscar a biblioteca go-retry e adicioná-la às dependências do seu projeto. Depois disso, você pode importá-la para o seu código como qualquer outro pacote Go.

Capítulo 3: Implementando Lógica Básica de Retentativa

3.1 Retentativa Simples com Espera Constante

A forma mais simples de lógica de retentativa envolve esperar por uma duração constante entre cada tentativa de retentativa. Você pode usar o go-retry para realizar retentativas com uma espera constante.

Aqui está um exemplo de como usar a espera constante com go-retry:

package main

import (
  "context"
  "time"
  "github.com/sethvargo/go-retry"
)

func main() {
    ctx := context.Background()
    
    // Crie uma nova espera constante
    backoff := retry.NewConstant(1 * time.Second)

    // Envolva sua lógica de retentativa em uma função que será passada para retry.Do
    operacao := func(ctx context.Context) error {
        // Seu código aqui. Retorne retry.RetryableError(err) para tentar novamente ou nil para parar.
        // Exemplo:
        // err := algumaOperacao()
        // if err != nil {
        //   return retry.RetryableError(err)
        // }
        // return nil

        return nil
    }
    
    // Use retry.Do com o contexto desejado, estratégia de espera e a operação
    if err := retry.Do(ctx, backoff, operacao); err != nil {
        // Lidar com o erro
    }
}

Neste exemplo, a função retry.Do continuará tentando a função operacao a cada 1 segundo até que tenha sucesso ou o contexto expire ou seja cancelado.

3.2 Implementando Espera Exponencial

A espera exponencial aumenta o tempo de espera entre as retentativas de forma exponencial. Essa estratégia ajuda a reduzir a carga sobre o sistema e é especialmente útil ao lidar com sistemas de grande escala ou serviços em nuvem.

Como usar a espera exponencial com go-retry é da seguinte forma:

package main

import (
  "context"
  "time"
  "github.com/sethvargo/go-retry"
)

func main() {
    ctx := context.Background()

    // Crie uma nova espera exponencial
    backoff := retry.NewExponential(1 * time.Second)

    // Forneça sua operação de retentativa
    operacao := func(ctx context.Context) error {
        // Implemente a operação conforme mostrado anteriormente
        return nil
    }
    
    // Use retry.Do para executar a operação com espera exponencial
    if err := retry.Do(ctx, backoff, operacao); err != nil {
        // Lidar com o erro
    }
}

No caso da espera exponencial, se a espera inicial for definida como 1 segundo, as retentativas acontecerão após 1 segundo, 2 segundos, 4 segundos, etc., aumentando exponencialmente o tempo de espera entre as retentativas subsequentes.

3.3 Estratégia de Recuo Fibonacci

A estratégia de recuo Fibonacci utiliza a sequência de Fibonacci para determinar o tempo de espera entre as tentativas, o que pode ser uma boa estratégia para problemas relacionados à rede, onde um timeout crescente gradual é benéfico.

A implementação do recuo Fibonacci com go-retry é demonstrada abaixo:

pacote principal

import (
  "context"
  "tempo"
  "github.com/sethvargo/go-retry"
)

func main() {
    ctx := context.Background()

    // Criar um novo recuo Fibonacci
    recuo := retry.NewFibonacci(1 * tempo.Segundo)

    // Definir uma operação para tentar novamente
    operação := func(ctx context.Context) error {
        // Aqui estaria a lógica para realizar a ação que pode falhar e precisa ser tentada novamente
        return nil
    }
    
    // Executar a operação com recuo Fibonacci usando retry.Do
    se err := retry.Do(ctx, recuo, operação); err != nil {
        // Lidar com o erro
    }
}

Com um recuo Fibonacci com um valor inicial de 1 segundo, as tentativas ocorrerão após 1s, 1s, 2s, 3s, 5s, etc., seguindo a sequência de Fibonacci.

Capítulo 4: Técnicas Avançadas de Retentativa e Middleware

4.1 Utilizando Jitter em Retentativas

Ao implementar lógica de retentativa, é importante considerar o impacto das retentativas simultâneas em um sistema, o que pode levar a um problema de manada. Para mitigar esse problema, podemos adicionar jitter aleatório aos intervalos de recuo. Essa técnica ajuda a escalonar as tentativas de retentativa, reduzindo a probabilidade de múltiplos clientes tentarem novamente simultaneamente.

Exemplo de adição de jitter:

b := retry.NewFibonacci(1 * tempo.Segundo)

// Retornar o próximo valor, +/- 500ms
b = retry.WithJitter(500 * tempo.Milissegundo, b)

// Retornar o próximo valor, +/- 5% do resultado
b = retry.WithJitterPercentual(5, b)

4.2 Definindo Número Máximo de Retentativas

Em alguns cenários, é necessário limitar o número de tentativas de retentativa para evitar retentativas prolongadas e ineficazes. Ao especificar o número máximo de retentativas, podemos controlar o número de tentativas antes de desistir da operação.

Exemplo de definição de número máximo de retentativas:

b := retry.NewFibonacci(1 * tempo.Segundo)

// Parar após 4 retentativas, quando a 5ª tentativa falhar
b = retry.WithMaxRetries(4, b)

4.3 Limitando Durações Individuais de Recuo

Para garantir que as durações individuais de recuo não excedam um determinado limite, podemos usar o middleware CappedDuration. Isso evita que intervalos de recuo excessivamente longos sejam calculados, adicionando previsibilidade ao comportamento de retentativa.

Exemplo de limitação de durações individuais de recuo:

b := retry.NewFibonacci(1 * tempo.Segundo)

// Garantir que o valor máximo seja de 2s
b = retry.WithCappedDuration(2 * tempo.Segundo, b)

4.4 Controlando a Duração Total da Retentativa

Em cenários onde precisa haver um limite na duração total para todo o processo de retentativa, o middleware WithMaxDuration pode ser usado para especificar um tempo total máximo de execução. Isso garante que o processo de retentativa não continue indefinidamente, impondo um orçamento de tempo para as retentativas.

Exemplo de controle da duração total da retentativa:

b := retry.NewFibonacci(1 * tempo.Segundo)

// Garantir que o tempo total máximo de retentativa seja de 5s
b = retry.WithMaxDuration(5 * tempo.Segundo, b)