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