3.5 KiB
← Dead-lettering · Module README · Changing your mind →
5. Acknowledgement
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:
@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; 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.