Three Core Mechanisms
1. Publisher Confirms
Problem: basicPublish is fire-and-forget — the message is sent but there is no guarantee RabbitMQ actually received it.
Solution: Enable Publisher Confirms mode. RabbitMQ sends an ACK back after durably writing the message.
// Enable confirm mode when creating the Channel
channel.confirmSelect();
// Publish the message
channel.basicPublish("", QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN, body);
// Wait for RabbitMQ's ACK (max 5 seconds)
channel.waitForConfirmsOrDie(5000);
// If timeout or NACK → throws exception → checkout returns 500
// If ACK received → continues normally → checkout returns 200Cart RabbitMQ
│ │
│── basicPublish ─────────→│
│ │ written to disk
│←──────── ACK ───────────│
│ │
return 200 to client
Why it matters: Without Publisher Confirms, a message could be lost in transit while Cart already told the client that checkout succeeded.
2. Manual ACK
Problem: With autoAck=true, a message is deleted from the queue the moment it is delivered to the consumer. If the consumer crashes mid-processing, the message is permanently lost.
Solution: autoAck=false — the consumer manually ACKs only after successfully processing the message.
Happy path:
RabbitMQ Warehouse Consumer
│ │
│── deliver message ───────→│
│ (message still in queue)│ processing...
│ │
│ processed successfully
│←── basicAck ─────────────│ ← deleted from queue now
│
message deleted ✅
Consumer crash:
RabbitMQ Warehouse Consumer
│ │
│── deliver message ───────→│
│ (message still in queue)│ processing...
│ │
│ crash 💥
│
message re-queued ♻️ → delivered to another consumer
Code:
// Success
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
// Failure — requeue=true puts the message back
channel.basicNack(delivery.getEnvelope().getDeliveryTag(), false, true);3. Channel Pool
Problem: Creating a new Channel for every checkout request and closing it afterwards is expensive. Under high concurrency this becomes a bottleneck.
Connection vs Channel:
Connection (TCP — expensive to create, should be reused)
└── Channel 1 ← lightweight, many channels per connection
└── Channel 2
└── Channel 3
...
Solution: Pre-create a pool of Channels and reuse them across requests.
// Configuration (RabbitMQConfig.java)
config.setMaxTotal(20); // max 20 Channels in the pool
config.setMinIdle(5); // keep at least 5 idle
config.setMaxWaitMillis(5000); // wait up to 5s if pool is exhausted
config.setTestOnBorrow(true); // validate Channel is open before borrowing
// Usage (RabbitMQPublisher.java)
Channel channel = channelPool.borrowObject(); // borrow from pool
try {
channel.basicPublish(...);
channel.waitForConfirmsOrDie(5000);
} finally {
channelPool.returnObject(channel); // return to pool
}100 concurrent requests
│
▼
Channel Pool (20 channels)
├── Ch-1 borrowed
├── Ch-2 borrowed
├── ...
├── Ch-20 borrowed
└── request 21 waits (up to 5s)
Comparison:
| New Channel per request | Channel Pool | |
|---|---|---|
| Overhead | Create + close every time | Created once, reused forever |
| High concurrency | Poor performance | 20 channels serving requests concurrently |
| Resource usage | Uncontrolled | Capped at 20 |