Retry, Backoff e Circuit Breaker

Três tentativas antes da DLQ

Encadeando retry com envio automático para payment.dlq após falhas persistentes.

Avançado 40 min 35 pontos Leitura 0%

Nesta aula você vai

  • Aplicar limite de 3 tentativas antes da DLQ
  • Transportar metadados de tentativa em headers
  • Garantir fluxo auditável no payment-service

Três tentativas antes da DLQ

Objetivos

  • Garantir retry controlado antes de descartar para DLQ.
  • Padronizar metadados de tentativas.
  • Reduzir impacto de falhas persistentes no throughput.

Pré-requisitos

  • Aula de retry exponencial concluída.
  • Publisher de DLQ implementado na matéria 9.
  • Consumer Kafka em execução no payment-service.

Conceito

Política alvo: tentar até 3 vezes em erro transitório. Se todas falharem, enviar para payment.dlq com headers de rastreio. Isso evita retries infinitos e facilita operação.

Estrutura de arquivos

services/payment-service/internal/
├── consumer/payment_consumer.go
├── retry/runner.go
└── dlq/publisher.go

Passo a passo

  1. Fluxo no consumer:
func (c *Consumer) processWithRetry(ctx context.Context, msg kafka.Message, event PaymentRequested) error {
  attempts := 0
  err := retry.Run(ctx, c.retryPolicy, func() error {
    attempts++
    c.logger.Info("tentativa de autorização", "attempt", attempts, "orderId", event.OrderID)
    return c.gateway.Authorize(ctx, event.OrderID, event.AmountCents)
  }, errors.IsRetryable)

  if err == nil {
    return nil
  }

  if errors.IsRetryable(err) {
    return c.dlqPublisher.Publish(ctx, msg, err, attempts)
  }
  return c.dlqPublisher.Publish(ctx, msg, err, attempts)
}
  1. Propague header de tentativas:
func appendAttemptHeader(msg kafka.Message, attempts int) kafka.Message {
  msg.Headers = append(msg.Headers, kafka.Header{
    Key:   "x-retry-attempts",
    Value: []byte(strconv.Itoa(attempts)),
  })
  return msg
}
  1. Ajuste publisher DLQ para ler tentativas finais:
func (p *Publisher) Publish(ctx context.Context, original kafka.Message, reason error, attempts int) error {
  original = appendAttemptHeader(original, attempts)
  // ... envia para payment.dlq
  return p.writer.WriteMessages(ctx, original)
}

Como testar

Simule gateway indisponível:

docker compose up -d kafka payment-service
echo '{"type":"payment.requested","payload":{"orderId":"o-99","amountCents":4500}}' \
  | kcat -P -b localhost:9092 -t payment.events
docker compose logs -f payment-service
kcat -C -b localhost:9092 -t payment.dlq -o end -e -f 'Headers=%h Value=%s\n'

Valide x-retry-attempts=3 no evento enviado para DLQ.

Dicas de projeto

  • Fixe política de retry em configuração central.
  • Evite variação de tentativas por serviço sem justificativa.
  • Logue tentativa e tempo de espera entre elas.

Erros comuns

  • Enviar para DLQ antes de completar 3 tentativas.
  • Não incluir metadado de tentativas no evento.
  • Tratar erro transitório e erro de payload do mesmo jeito.

Resumo

Você encadeou retry exponencial com política de três tentativas antes da DLQ, com rastreabilidade completa por header.