Acknowledgement, code di dead-letter e retry
Garantisca la consegna con ack manuali, gestisca i messaggi non elaborabili e implementi flussi di retry e dead-letter.
Acknowledgement, code di dead-letter e retry è una lezione Node.js Backend Development Bootcamp gratuita su CoddyKit. Questa è la lezione 3 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento Node.js Backend Development Bootcamp, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso Node.js Backend Development Bootcamp include 4 lezioni in totale.
Parti di questa lezione non sono ancora state tradotte e vengono mostrate in inglese.
Why Acknowledgements Matter
By default, when a consumer receives a message from RabbitMQ it must tell the broker whether the message was processed successfully. This signal is called an acknowledgement (ack).
If you enable auto-ack, RabbitMQ deletes a message the instant it is delivered. If your worker then crashes mid-processing, the message is gone forever. With manual acks, the broker keeps the message until you confirm it, and re-delivers it if the consumer dies.
- auto-ack: fast, but at-most-once delivery (can lose messages)
- manual ack: safe, gives you at-least-once delivery
For any job that does real work (charging a card, sending email, writing to a DB), you almost always want manual acks.
Consuming With Manual Acks
Using the amqplib library, you pass { noAck: false } to channel.consume to enable manual acknowledgement. After your handler finishes successfully, you call channel.ack(msg).
Until you ack, RabbitMQ considers the message unacked and will redeliver it if the channel or connection closes.
const amqp = require('amqplib');
async function start() {
const conn = await amqp.connect('amqp://localhost');
const channel = await conn.createChannel();
const queue = 'orders';
await channel.assertQueue(queue, { durable: true });
channel.consume(queue, async (msg) => {
if (msg === null) return;
const order = JSON.parse(msg.content.toString());
console.log('Processing order', order.id);
// ... do real work here ...
channel.ack(msg); // confirm success
}, { noAck: false });
}
start();ack, nack, and reject
RabbitMQ gives you three ways to respond to a delivered message:
channel.ack(msg)— success, remove the messagechannel.nack(msg, false, requeue)— failure; ifrequeue=truethe message goes back to the queue, iffalseit is discarded (or dead-lettered)channel.reject(msg, requeue)— like nack but for a single message only
The second argument of nack is allUpTo. Set it to false to act only on the current message; true nacks every unacked message up to this one.
Key decision: requeue=true retries immediately and can cause infinite hot-loops for poison messages. Prefer requeue=false plus a dead-letter strategy.
// Failure with no requeue -> message is dropped or dead-lettered
channel.nack(msg, false, false);
// Failure with requeue -> message returns to the front of the queue
channel.nack(msg, false, true);
// reject only ever affects this one message
channel.reject(msg, false);Prefetch: Controlling In-Flight Work
Without limits, RabbitMQ pushes as many messages as it can to a consumer, all sitting unacked in memory. That can overwhelm a slow worker.
channel.prefetch(n) sets the maximum number of unacknowledged messages a consumer may hold at once. With manual acks, a worker with prefetch(1) processes one message, acks it, then receives the next — giving fair, back-pressured dispatch.
const channel = await conn.createChannel();
await channel.assertQueue('orders', { durable: true });
// Only one unacked message at a time per consumer
await channel.prefetch(1);
channel.consume('orders', async (msg) => {
await handle(msg);
channel.ack(msg);
}, { noAck: false });What Is a Poison Message?
A poison message is one that always fails processing — bad JSON, a reference to a deleted record, an unrecoverable validation error. If you simply nack(msg, false, true) it, the message requeues, gets redelivered, fails again, and loops forever, pinning your CPU.
You need a way to give up after a few tries and move the message somewhere safe for inspection. That somewhere is a Dead-Letter Queue (DLQ).
- Stop retrying after N attempts
- Move the message out of the main flow
- Keep it for debugging / manual replay
Dead-Letter Exchanges
RabbitMQ can automatically route a message to another exchange when it is dead-lettered. A message is dead-lettered when:
- it is nacked/rejected with
requeue=false, or - its TTL expires, or
- the queue exceeds its max length
You configure this with the queue arguments x-dead-letter-exchange (required) and optionally x-dead-letter-routing-key. Bind a queue to that exchange and dead messages land there.
// Main queue routes failures to the 'dlx' exchange
await channel.assertExchange('dlx', 'direct', { durable: true });
await channel.assertQueue('orders.dlq', { durable: true });
await channel.bindQueue('orders.dlq', 'dlx', 'orders.dead');
await channel.assertQueue('orders', {
durable: true,
arguments: {
'x-dead-letter-exchange': 'dlx',
'x-dead-letter-routing-key': 'orders.dead'
}
});Sending a Failure to the DLQ
Once the orders queue has a dead-letter exchange configured, dropping a message there is simply a matter of nacking without requeue.
The broker handles the routing for you — you never publish to the DLQ manually. This keeps your consumer logic clean: process, and on unrecoverable failure, let RabbitMQ dead-letter it.
channel.consume('orders', async (msg) => {
try {
const order = JSON.parse(msg.content.toString());
await processOrder(order);
channel.ack(msg);
} catch (err) {
console.error('Unrecoverable failure:', err.message);
// requeue=false -> RabbitMQ dead-letters to orders.dlq
channel.nack(msg, false, false);
}
}, { noAck: false });Counting Retries With Headers
Often you want to retry a few times before giving up — a transient DB timeout might succeed on the second try. RabbitMQ has no built-in retry counter, so you track attempts yourself.
When a message is dead-lettered, RabbitMQ adds an x-death header array describing each dead-letter event, including a count. You can read it to decide whether to retry or to route to the final DLQ.
function deathCount(msg) {
const xDeath = msg.properties.headers && msg.properties.headers['x-death'];
if (!Array.isArray(xDeath) || xDeath.length === 0) return 0;
// sum counts across dead-letter events for this reason
return xDeath.reduce((sum, d) => sum + (d.count || 0), 0);
}
const attempts = deathCount(msg);
if (attempts >= 3) {
channel.nack(msg, false, false); // give up -> parking DLQ
} else {
// route back for another attempt
}Delayed Retries With a TTL Wait Queue
Immediate requeue retries instantly, which is bad for transient errors that need time to clear. A common pattern is a retry queue with a TTL that dead-letters back to the main queue.
Flow: main queue fails → message goes to orders.retry which has x-message-ttl (e.g. 5s) and a dead-letter exchange pointing back to orders. After the TTL expires, RabbitMQ moves it back automatically — giving a built-in delay between attempts.
// Retry queue: holds messages for 5s, then dead-letters back to main
await channel.assertQueue('orders.retry', {
durable: true,
arguments: {
'x-message-ttl': 5000,
'x-dead-letter-exchange': '', // default exchange
'x-dead-letter-routing-key': 'orders' // back to main queue
}
});
// On a retryable failure, publish into the wait queue instead of nacking
channel.sendToQueue('orders.retry', msg.content, {
persistent: true,
headers: msg.properties.headers
});
channel.ack(msg); // ack the original; the copy is now waitingPutting the Retry Logic Together
Here is the decision flow a robust consumer follows on failure:
- Parse/validate fails permanently → dead-letter to the parking DLQ immediately
- Transient error and attempts < max → push to the retry (TTL) queue for a delayed re-attempt
- Transient error but attempts ≥ max → give up, send to the parking DLQ
This bounds total work, avoids hot-loops, and preserves failed messages for inspection or manual replay.
const MAX_RETRIES = 3;
channel.consume('orders', async (msg) => {
let order;
try {
order = JSON.parse(msg.content.toString());
} catch (e) {
return channel.nack(msg, false, false); // poison -> parking DLQ
}
try {
await processOrder(order);
channel.ack(msg);
} catch (err) {
const attempts = retryHeader(msg);
if (attempts >= MAX_RETRIES) {
channel.nack(msg, false, false); // exhausted -> parking DLQ
} else {
channel.sendToQueue('orders.retry', msg.content, {
persistent: true,
headers: { ...msg.properties.headers, 'x-retries': attempts + 1 }
});
channel.ack(msg); // ack original; retry is queued
}
}
}, { noAck: false });A Runnable Retry Simulation
The RabbitMQ behavior needs a broker, but the retry decision logic is plain JavaScript you can reason about and test in isolation. Below is a self-contained simulation of the ack / retry / dead-letter decision an order consumer makes.
It demonstrates the core idea: succeed and ack, retry while under the limit, and park to the DLQ once retries are exhausted.
const MAX_RETRIES = 3;
function decide(message) {
// simulate processing: 'good' succeeds, 'bad-json' is poison, else transient
if (message.kind === 'good') return { action: 'ack' };
if (message.kind === 'bad-json') return { action: 'dlq', reason: 'poison' };
const attempts = message.retries || 0;
if (attempts >= MAX_RETRIES) return { action: 'dlq', reason: 'exhausted' };
return { action: 'retry', retries: attempts + 1 };
}
const inbox = [
{ id: 1, kind: 'good' },
{ id: 2, kind: 'bad-json' },
{ id: 3, kind: 'transient', retries: 0 },
{ id: 4, kind: 'transient', retries: 3 }
];
for (const msg of inbox) {
const result = decide(msg);
console.log('msg ' + msg.id + ' ->', JSON.stringify(result));
}Quick Check
You have a consumer using manual acks. A message contains malformed JSON that can never be parsed. You want to stop it from looping forever while keeping it for later inspection.
Recap
You learned how to make RabbitMQ delivery reliable and resilient:
- Manual acks (
noAck: false+channel.ack) give at-least-once delivery so a crash never loses a message. - nack/reject with
requeue=falsehands a failed message to a dead-letter exchange instead of looping forever. - prefetch(n) back-pressures slow workers by capping unacked messages.
- Dead-letter exchanges (
x-dead-letter-exchange) auto-route failures to a DLQ for inspection and replay. - Retries use a TTL wait queue that dead-letters back to the main queue, with an attempt counter (via
x-deathor a custom header) to cap retries before parking the message.
Together these patterns give you bounded, observable, lossless message processing.
Domande Frequenti
La lezione «Acknowledgement, code di dead-letter e retry» è gratuita?
Sì — il testo completo di «Acknowledgement, code di dead-letter e retry» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso Node.js Backend Development Bootcamp, passa a CoddyKit PRO. Il corso Node.js Backend Development Bootcamp include 4 lezioni in totale.
Cosa imparerò in «Acknowledgement, code di dead-letter e retry»?
Garantisca la consegna con ack manuali, gestisca i messaggi non elaborabili e implementi flussi di retry e dead-letter. Eserciti Node.js Backend Development Bootcamp con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.
Ho bisogno di esperienza per iniziare Node.js Backend Development Bootcamp?
Non è richiesta alcuna esperienza precedente. Node.js Backend Development Bootcamp su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 3 di 4.
Quanto tempo richiede la lezione «Acknowledgement, code di dead-letter e retry»?
La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.
Posso scrivere ed eseguire codice in questa lezione Node.js Backend Development Bootcamp?
Sì. Ogni lezione Node.js Backend Development Bootcamp include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.
Tutte le lezioni di questo corso
- Producer, consumer e modello AMQP
- Tipi di exchange: Direct, Topic, Fanout e Headers
- Acknowledgement, code di dead-letter e retry
- Work queue, prefetch e consumer concorrenti