Step 3: Consumer with Retry Counter
msgs, _ := ch.Consume("main_queue", "", false, false, false, false, nil)
for d := range msgs {
retries := 0
if val, ok := d.Headers["x-retry"].(int32); ok {
retries = int(val)
}
log.Printf("Got: %s (retry=%d)", d.Body, retries)
// simulate failure
failed := true
if failed {
if retries < 2 { // <3 attempts total
log.Println("Retrying...")
// republish with incremented retry counter
ch.Publish("", d.RoutingKey, false, false, amqp.Publishing{
ContentType: "text/plain",
Body: d.Body,
Headers: amqp.Table{"x-retry": int32(retries + 1)},
})
d.Ack(false) // ack current, we already re-published
} else {
log.Println("Max retries reached → send to DLQ")
d.Nack(false, false) // requeue=false → goes to DLQ
}
} else {
d.Ack(false) // success
}
}
Step 4: DLQ Consumer
msgsDLQ, _ := ch.Consume("dead_letters", "", true, false, false, false, nil)
for d := range msgsDLQ {
log.Printf("[DLQ] %s", d.Body)
}
How It Works
- Producer sends message with
x-retry=0. - Consumer tries to process. If fail → republishes with
x-retry=1. - After 2 more failures (
x-retry=2), the consumer rejects withrequeue=false. - RabbitMQ routes the message to DLQ.
- Operators can inspect DLQ to debug or retry later.
Best Practices
- Use headers to track retries (RabbitMQ doesn’t track it automatically).
- Delay retries (e.g., use a delay exchange or TTL queue) to avoid hot loops.
- Alert on DLQ growth, if DLQ fills up, something is wrong upstream.
- Separate DLQs per service, makes debugging easier.
Conclusion
Dead Letter Queues are a powerful way to handle failed messages in RabbitMQ.
By adding a retry mechanism with a counter, we can avoid sending messages to DLQ too early.
In our example, messages get 3 attempts:
- 1st try + 2 retries → if still failing → goes to DLQ.
This design makes systems more resilient and gives developers a chance to fix transient issues before moving messages into permanent failure storage.
Pages: 1 2
Category: RabbitMQ
