Bootcamp i backendutveckling med Node.js · Lektion

Bekräftelser, dead-letter-köer och omförsök

Säkerställ leverans med manuella acks, hantera giftiga meddelanden och implementera flöden för omförsök och dead letters.

Lektion 3 av 413 steg

Bekräftelser, dead-letter-köer och omförsök är en gratis lektion i Bootcamp i backendutveckling med Node.js på CoddyKit. Detta är lektion 3 av 4. Ni kan läsa hela lektionen gratis nedan och sedan öva praktiskt i webbläsaren med en inbyggd kodredigerare och en AI-handledare som är tillgänglig dygnet runt. Den ingår i lärvägen för Bootcamp i backendutveckling med Node.js, och Era framsteg synkroniseras mellan webben och CoddyKit-appen. Kursen i Bootcamp i backendutveckling med Node.js innehåller totalt 4 lektioner.

Varför bekräftelser är viktiga

Som standard måste en konsument som tar emot ett meddelande från RabbitMQ tala om för brokern om meddelandet behandlades korrekt. Denna signal kallas en bekräftelse (ack).

Om ni aktiverar auto-ack tar RabbitMQ bort meddelandet så fort det levereras. Om er worker sedan kraschar mitt under behandlingen är meddelandet borta för alltid. Med manuella ack behåller brokern meddelandet tills ni bekräftar det, och levererar det igen om konsumenten avslutas.

  • auto-ack: snabbt, men leverans högst en gång (meddelanden kan gå förlorade)
  • manuellt ack: säkert, ger leverans minst en gång

För alla jobb som faktiskt utför arbete (debiterar ett kort, skickar e-post eller skriver till en databas) vill ni nästan alltid använda manuella ack.

Konsumera med manuella ack

Med biblioteket amqplib skickar ni { noAck: false } till channel.consume för att aktivera manuella bekräftelser. När hanteraren har slutförts utan fel anropar ni channel.ack(msg).

Tills ni skickar ett ack betraktar RabbitMQ meddelandet som obekräftat och levererar det igen om kanalen eller anslutningen stängs.

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 och reject

RabbitMQ ger er tre sätt att svara på ett levererat meddelande:

  • channel.ack(msg) — lyckades, ta bort meddelandet
  • channel.nack(msg, false, requeue) — misslyckades; om requeue=true läggs meddelandet tillbaka i kön, och om false tas det bort eller skickas till dead-letter
  • channel.reject(msg, requeue) — som nack, men endast för ett enskilt meddelande

Det andra argumentet till nack är allUpTo. Sätt det till false för att endast agera på det aktuella meddelandet; true skickar nack för alla obekräftade meddelanden fram till detta.

Viktigt beslut: requeue=true försöker igen omedelbart och kan orsaka oändliga snabbslingor för meddelanden som inte går att behandla. Föredra requeue=false tillsammans med en dead-letter-strategi.

// 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: styrning av pågående arbete

Utan begränsningar skickar RabbitMQ så många meddelanden som möjligt till en konsument, och alla ligger obekräftade i minnet. Det kan överbelasta en långsam worker.

channel.prefetch(n) anger det maximala antalet obekräftade meddelanden som en konsument får hålla samtidigt. Med manuella bekräftelser bearbetar en worker med prefetch(1) ett meddelande, bekräftar det och tar sedan emot nästa — vilket ger en rättvis distribution med backpressure.

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 });

Vad är ett poison-meddelande

Ett poison-meddelande är ett meddelande som alltid misslyckas vid bearbetning — till exempel ogiltig JSON, en referens till en borttagen post eller ett valideringsfel som inte går att återställa från. Om du bara gör nack(msg, false, true) köas meddelandet om, levereras igen, misslyckas på nytt och hamnar i en oändlig loop som binder upp processorn.

Du behöver ett sätt att ge upp efter några försök och flytta meddelandet till en säker plats för granskning. Den platsen är en Dead-Letter Queue (DLQ).

  • Sluta försöka igen efter N försök
  • Flytta bort meddelandet från huvudflödet
  • Behåll det för felsökning / manuell återuppspelning

Dead-Letter Exchanges

RabbitMQ kan automatiskt dirigera ett meddelande till ett annat exchange när det blir dead-lettered. Ett meddelande blir dead-lettered när:

  • det nacks/avvisas med requeue=false, eller
  • dess TTL löper ut, eller
  • kön överskrider sin maximala längd

Du konfigurerar detta med köargumenten x-dead-letter-exchange (obligatoriskt) och valfritt x-dead-letter-routing-key. Bind en kö till det exchanget, så hamnar de döda meddelandena där.

// 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'
  }
});

Skicka ett fel till DLQ

När kön orders har konfigurerats med ett dead-letter exchange är det enkelt att skicka ett meddelande dit: nacca det utan omköning.

Broker hanterar routningen åt dig — du publicerar aldrig manuellt till DLQ. Det håller konsumentlogiken ren: bearbeta meddelandet och låt RabbitMQ dead-letter-hantera det vid fel som inte går att återställa från.

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 });

Räkna omförsök med headers

Ofta vill du försöka igen några gånger innan du ger upp — ett tillfälligt timeout-fel mot databasen kanske lyckas vid det andra försöket. RabbitMQ har ingen inbyggd räknare för omförsök, så du måste själv hålla reda på försöken.

När ett meddelande blir dead-lettered lägger RabbitMQ till en array med headern x-death som beskriver varje dead-letter-händelse, inklusive en count. Du kan läsa den för att avgöra om meddelandet ska försöka igen eller dirigeras till den slutliga DLQ:n.

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
}

Fördröjda omförsök med en TTL-väntkö

En omedelbar omköning försöker igen direkt, vilket är olämpligt för tillfälliga fel som behöver tid för att försvinna. Ett vanligt mönster är en retry-kö med TTL som dead-letter-dirigerar tillbaka till huvudkön.

Flöde: huvudkön misslyckas → meddelandet går till orders.retry, som har x-message-ttl (t.ex. 5 s) och ett dead-letter exchange som pekar tillbaka på orders. När TTL:en löper ut flyttar RabbitMQ automatiskt tillbaka meddelandet — vilket ger en inbyggd fördröjning mellan försöken.

// 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 waiting

Sammanfoga logiken för omförsök

Här är beslutsflödet som en robust konsument följer vid fel:

  • Parsering eller validering misslyckas permanent → dead-letter-dirigera omedelbart till parking-DLQ
  • Tillfälligt fel och antal försök < max → skicka till retry-kön (TTL) för ett fördröjt nytt försök
  • Tillfälligt fel men antal försök ≥ max → ge upp och skicka till parking-DLQ

Det begränsar det totala arbetet, undviker täta loopar och bevarar misslyckade meddelanden för granskning eller manuell återuppspelning.

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 });

En körbar simulering av omförsök

RabbitMQ-beteendet kräver en broker, men beslutslogiken för omförsök är vanlig JavaScript som du kan resonera kring och testa isolerat. Nedan finns en fristående simulering av det beslut om ack / retry / dead-letter som en orderkonsument fattar.

Den visar grundidén: lyckas och ack:a, försök igen så länge gränsen inte har nåtts och parkera meddelandet i DLQ när omförsöken är slut.

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));
}

Snabb kontroll

Du har en konsument som använder manuella bekräftelser. Ett meddelande innehåller felaktig JSON som aldrig kan tolkas. Du vill hindra det från att loopa för evigt och samtidigt behålla det för senare granskning.

Sammanfattning

Du har lärt dig hur du gör meddelandeleveranser i RabbitMQ tillförlitliga och motståndskraftiga:

  • Manuella bekräftelser (noAck: false + channel.ack) ger leverans minst en gång, så en krasch gör aldrig att ett meddelande går förlorat.
  • nack/reject med requeue=false överlämnar ett misslyckat meddelande till ett dead-letter exchange i stället för att låta det loopa för evigt.
  • prefetch(n) ger backpressure för långsamma workers genom att begränsa antalet obekräftade meddelanden.
  • Dead-letter exchanges (x-dead-letter-exchange) dirigerar automatiskt fel till en DLQ för granskning och återuppspelning.
  • Omförsök använder en TTL-väntkö som dead-letter-dirigerar tillbaka till huvudkön, med en försöksräknare (via x-death eller en egen header) som begränsar antalet omförsök innan meddelandet parkeras.

Tillsammans ger dessa mönster en begränsad, observerbar och förlustfri meddelandebearbetning.

Gratis att börja

Lär dig JavaScript med en AI-lärare – gratis

Skriv och kör riktig kod i webbläsaren, få omedelbar hjälp av en AI-lärare dygnet runt och fortsätt där du slutade – på webben eller i appen.

Kurser
22
Lektioner
92

Vanliga frågor

Är lektionen ”Bekräftelser, dead-letter-köer och omförsök” gratis?

Ja – hela texten till ”Bekräftelser, dead-letter-köer och omförsök” kan läsas gratis här på webben. Om Ni vill öva interaktivt med en inbyggd kodredigerare och en AI-handledare som är tillgänglig dygnet runt och låsa upp resten av kursen i Bootcamp i backendutveckling med Node.js, kan Ni uppgradera till CoddyKit PRO. Kursen i Bootcamp i backendutveckling med Node.js innehåller totalt 4 lektioner.

Vad lär jag mig i ”Bekräftelser, dead-letter-köer och omförsök”?

Säkerställ leverans med manuella acks, hantera giftiga meddelanden och implementera flöden för omförsök och dead letters. Ni övar på Bootcamp i backendutveckling med Node.js med praktisk kod som körs direkt i webbläsaren, medan en AI-handledare som är tillgänglig dygnet runt svarar på Era frågor under lektionen.

Behöver jag någon erfarenhet för att börja lära mig Bootcamp i backendutveckling med Node.js?

Du behöver inga förkunskaper. Utbildningen i Bootcamp i backendutveckling med Node.js på CoddyKit är upplagd för allt från nybörjare till avancerade elever, så att du kan börja här eller från början och gå fram i din egen takt. Detta är lektion 3 av 4.

Hur lång tid tar lektionen ”Bekräftelser, dead-letter-köer och omförsök”?

De flesta CoddyKit-lektioner tar cirka 5–10 minuter. Varje lektion är kort och interaktiv, så att du gör stadiga framsteg och kan fortsätta precis där du slutade – på webben eller i appen.

Kan jag skriva och köra kod i den här Bootcamp i backendutveckling med Node.js-lektionen?

Ja. Varje Bootcamp i backendutveckling med Node.js-lektion innehåller en inbyggd kodredigerare, så att du kan skriva och köra riktig kod direkt i webbläsaren och få omedelbar AI-feedback – utan lokal installation.

Alla lektioner i den här kursen

  1. Producenter, konsumenter och AMQP-modellen
  2. Exchange-typer: Direct, Topic, Fanout och Headers
  3. Bekräftelser, dead-letter-köer och omförsök
  4. Arbetsköer, prefetch och konkurrerande konsumenter
← Tillbaka till Bootcamp i backendutveckling med Node.js