83 lines
3.5 KiB
Markdown
83 lines
3.5 KiB
Markdown
[← 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)
|