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 200
Cart                    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 requestChannel Pool
OverheadCreate + close every timeCreated once, reused forever
High concurrencyPoor performance20 channels serving requests concurrently
Resource usageUncontrolledCapped at 20