Add the rabbitmq module

This commit is contained in:
2026-08-29 09:58:34 +05:30
parent 3a682e496e
commit de9fc5ce4c
27 changed files with 1523 additions and 0 deletions

View File

@@ -0,0 +1,82 @@
[← Dead-lettering](04-dead-lettering.md) · [Module README](../README.md) · [Changing your mind →](06-changing-your-mind.md)
# 5. Acknowledgement
```yaml
spring:
rabbitmq:
listener:
simple:
acknowledge-mode: manual
prefetch: 1
```
Three modes, and the default is not the one you want for work that matters.
| Mode | Behaviour |
|---|---|
| `auto` (default) | the container acks after the listener returns, nacks on exception |
| `manual` | your code calls `basicAck` / `basicNack`; nothing else does |
| `none` | the broker forgets the message the moment it is delivered |
(Boot publishes no default for `acknowledge-mode` either; the field initialiser in
`AbstractMessageListenerContainer` is `AcknowledgeMode.AUTO`.)
`none` is genuinely fire-and-forget: no redelivery, ever, and no flow control either, because
`prefetch` is meaningless without acknowledgement. It is the right choice for metrics and the
wrong choice for everything else.
`auto` is a reasonable default and its name is misleading: it does not mean "auto-ack" in the
AMQP sense (that is `none`). It means the container decides, and it decides by whether your
listener threw.
`manual` is what you want when the acknowledgement must happen after some other durable side
effect — a database commit, a downstream call. In `manual` mode the listener takes a `Channel`
and a delivery tag:
```java
@RabbitListener(queues = "orders.work")
void onOrder(OrderMessage message, Channel channel,
@Header(AmqpHeaders.DELIVERY_TAG) long tag) throws IOException {
try {
process(message);
channel.basicAck(tag, false);
}
catch (TransientFailure ex) {
channel.basicNack(tag, false, !alreadyRedelivered);
}
catch (PermanentFailure ex) {
channel.basicNack(tag, false, false); // dead-letter it
}
}
```
The two catch blocks are the point. A single `catch (Exception)` that nacks with `requeue=true`
is the loop from [chapter 4](04-dead-lettering.md); a single one that nacks with `requeue=false`
dead-letters transient failures that would have succeeded on a retry. Deciding which kind of
failure you had is work you cannot delegate to the container.
## `prefetch` is the flow-control knob
`prefetch` is how many un-acknowledged messages the broker will send a consumer. Spring Boot sets
no default, so `AbstractMessageListenerContainer.DEFAULT_PREFETCH_COUNT` applies, and that
constant is **250** — throughput-oriented: a slow consumer holds 250 messages that no
other consumer can take, so one stuck worker parks 250 messages behind it.
Set it to 1 when messages are expensive and unevenly sized — it gives you round-robin
distribution across consumers. Leave it high when messages are small and uniform and you want
throughput. This is the single most effective RabbitMQ tuning parameter and it is almost never
touched.
## The forgotten ack
Not calling `basicAck` does not lose the message and does not throw. The message stays
un-acknowledged, counting against `prefetch`, until the channel closes. With `prefetch: 1` the
consumer stops after exactly one message; with the default it stops after 250. Both look like the
consumer "just stopped", hours apart in wall-clock time depending on traffic.
`rabbitmqctl list_queues name messages_ready messages_unacknowledged` is the command that
distinguishes them. A large `messages_unacknowledged` with a live consumer means somebody forgot
an ack.
[Changing your mind →](06-changing-your-mind.md)