RabbitMQ: Message Acknowledgment (ACK, NACK)


Message acknowledgment in RabbitMQ ensures reliable message delivery. It helps the broker determine whether a message has been processed successfully or needs to be resent.
This article covers:
What message acknowledgment is
Why it's important
Types of acknowledgments (ACK and NACK)
How to implement acknowledgments using RabbitMQ
A flowchart for visualization
Example code in Golang
๐งโ๐ป What is Message Acknowledgment?
Message acknowledgment allows consumers to inform RabbitMQ whether a message was processed successfully.
ACK (Acknowledgment): Informs RabbitMQ that a message was successfully processed and can be removed from the queue.
NACK (Negative Acknowledgment): Informs RabbitMQ that the message processing failed and should be requeued or discarded.
๐ Why is it Important?
Ensures no message loss during failures
Prevents duplicate message processing
Provides fault-tolerant and reliable systems
๐ Flowchart: How Acknowledgment Works in RabbitMQ
+-------------------+
| Producer |
+-------------------+
|
โ
+-----------------------+
| Exchange |
+-----------------------+
|
โ
+---------+
| Queue |
+---------+
โ
+------------+
| Consumer |
+------------+
/ \
ACK / \ NACK
+-------+ +-------+
|Remove | |Requeue|
|Message| |Message|
+-------+ +-------+
ACK: Removes the message from the queue.
NACK: Returns the message to the queue for reprocessing.
๐ข Types of Acknowledgments
โ ACK (Acknowledgment)
Sent when a message is successfully processed.
RabbitMQ removes the message from the queue.
Prevents duplicate processing.
โ NACK (Negative Acknowledgment)
Sent when message processing fails.
RabbitMQ can requeue the message for retry or discard it.
Useful for error handling and fault tolerance.
๐ Reject
Similar to NACK but does not support bulk rejection.
Allows selective discarding of messages without requeuing.
๐ Example: Implementing Acknowledgment in Golang
๐ฆ Producer Code
The producer sends messages to a specified queue.
package main
import (
"log"
"github.com/streadway/amqp"
)
func main() {
// Establish connection to RabbitMQ server
conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
if err != nil {
log.Fatal(err)
}
defer conn.Close() // Ensure the connection is closed when the function exits
// Open a channel for communication with RabbitMQ
ch, err := conn.Channel()
if err != nil {
log.Fatal(err)
}
defer ch.Close() // Ensure the channel is closed when done
// Declare a queue named "ack_queue"
// Non-durable, non-auto-delete, non-exclusive, and no additional arguments
q, err := ch.QueueDeclare("ack_queue", false, false, false, false, nil)
if err != nil {
log.Fatal(err)
}
// Publish 5 messages to the queue
for i := 0; i < 5; i++ {
// Construct message body with a simple numbering
body := "Message " + string(rune(i + '0'))
// Publish the message to the queue
err = ch.Publish(
"", // Exchange (empty means default exchange)
q.Name, // Routing key (queue name)
false, // Mandatory (false = discard message if no queue)
false, // Immediate (false = allow broker to queue the message)
amqp.Publishing{
ContentType: "text/plain",
Body: []byte(body),
},
)
if err != nil {
log.Printf("Failed to send message: %v", err)
}
// Log successful message publishing
log.Printf(" [x] Sent %s", body)
}
}
๐งโ๐ป Consumer with ACK and NACK
The consumer reads messages and sends acknowledgments based on the processing result.
package main
import (
"log"
"github.com/streadway/amqp"
)
func main() {
// Step 1: Establish connection to RabbitMQ server
conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
if err != nil {
log.Fatal(err)
}
defer conn.Close() // Ensure the connection is closed when the function exits
// Step 2: Open a channel for communication with RabbitMQ
ch, err := conn.Channel()
if err != nil {
log.Fatal(err)
}
defer ch.Close() // Ensure the channel is closed when done
// Step 3: Declare a queue named "ack_queue"
// Non-durable, non-auto-delete, non-exclusive, and no additional arguments
q, err := ch.QueueDeclare("ack_queue", false, false, false, false, nil)
if err != nil {
log.Fatal(err)
}
// Step 4: Consume messages from the queue
msgs, err := ch.Consume(
"ack_queue", "", false, false, false, false, nil,
)
if err != nil {
log.Fatal(err)
}
// Step 5: Process and acknowledge messages
for msg := range msgs {
log.Printf("Received: %s", msg.Body)
// Simulating error scenario for specific messages
if string(msg.Body) == "Message 2" {
log.Printf("NACK Message: %s", msg.Body)
// Step 5a: Send NACK to requeue the message
msg.Nack(false, true)
} else {
log.Printf("ACK Message: %s", msg.Body)
// Step 5b: Send ACK to confirm successful processing
msg.Ack(false)
}
}
}
๐ Explanation
msg.Ack(false): Confirms successful processing, removing the message.
msg.Nack(false, true): Rejects and requeues the message.
msg.Reject(false): Rejects without requeuing.
Key Arguments
false
inmsg.Ack(false)
: Acknowledges a single message.true
inmsg.Nack(false, true)
: Requeues the message.false
inmsg.Nack(false, false)
: Discards the message.false
inch.Consume(auto-ack=false)
: Ensures manual acknowledgment.
โ Key Takeaways
Use ACK for reliable message processing.
Use NACK for error handling and retries.
Enable durable queues for message persistence.
Monitor unacknowledged messages to detect failures.
This ensures a robust message processing system using RabbitMQ.
Subscribe to my newsletter
Read articles from Shivam Dubey directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
