Retry, Backoff e Circuit Breaker
Retry simples, exponencial e backoff
Implementando retry com backoff exponencial e jitter no payment-service.
Nesta aula você vai
- Implementar retry exponencial com jitter em Go
- Aplicar somente em erros transitórios
- Registrar tentativas com observabilidade
Retry simples, exponencial e backoff
Objetivos
- Melhorar taxa de sucesso em falhas transitórias.
- Evitar rajada de tentativas simultâneas.
- Preparar base para política "3 tentativas e DLQ".
Pré-requisitos
payment-serviceem Go/Gin.- Classificação de erro disponível (transitório vs irrecuperável).
- Kafka consumidor já funcional.
Conceito
Retry exponencial aumenta o intervalo a cada tentativa (base * 2^n). O jitter adiciona aleatoriedade para evitar efeito manada quando várias instâncias falham ao mesmo tempo.
Estrutura de arquivos
services/payment-service/internal/
├── retry/policy.go
├── retry/runner.go
└── consumer/payment_consumer.go
Passo a passo
- Crie política em
retry/policy.go:
package retry
import (
"math"
"math/rand"
"time"
)
type Policy struct {
MaxAttempts int
BaseDelay time.Duration
MaxDelay time.Duration
}
func (p Policy) NextDelay(attempt int) time.Duration {
exp := float64(p.BaseDelay) * math.Pow(2, float64(attempt-1))
delay := time.Duration(exp)
if delay > p.MaxDelay {
delay = p.MaxDelay
}
jitter := time.Duration(rand.Int63n(int64(delay / 4)))
return delay + jitter
}
- Runner genérico em
retry/runner.go:
func Run(ctx context.Context, policy Policy, fn func() error, isRetryable func(error) bool) error {
var lastErr error
for attempt := 1; attempt <= policy.MaxAttempts; attempt++ {
if err := fn(); err != nil {
lastErr = err
if !isRetryable(err) {
return err
}
if attempt == policy.MaxAttempts {
return err
}
delay := policy.NextDelay(attempt)
timer := time.NewTimer(delay)
select {
case <-ctx.Done():
timer.Stop()
return ctx.Err()
case <-timer.C:
}
continue
}
return nil
}
return lastErr
}
- Use no consumidor:
err := retry.Run(ctx, retry.Policy{
MaxAttempts: 3,
BaseDelay: 200 * time.Millisecond,
MaxDelay: 2 * time.Second,
}, func() error {
return c.gateway.Authorize(ctx, event.OrderID, event.AmountCents)
}, errors.IsRetryable)
Como testar
docker compose up -d payment-service kafka
docker compose logs -f payment-service
Simule instabilidade temporária no gateway e observe no log:
attempt=1 delay_ms=200
attempt=2 delay_ms=400
attempt=3 delay_ms=800
Dicas de projeto
- Sempre combine retry com timeout de contexto.
- Defina
MaxDelaypara evitar latência infinita. - Métrica por tentativa ajuda a ajustar parâmetros.
Erros comuns
- Retry em erro de validação de payload.
- Backoff fixo em vez de exponencial.
- Falta de jitter em ambiente com múltiplas réplicas.
Resumo
Você implementou retry exponencial com jitter no payment-service, aplicando apenas em erros transitórios.