Bootcamp i backendutvikling med Node.js · leksjon

Kvitteringer, dead-letter-køer og retries

Garanter levering med manuelle acks, håndter meldinger som ikke kan behandles, og implementer retry- og dead-letter-flyter.

Leksjon 3 av 413 trinn

Kvitteringer, dead-letter-køer og retries er en gratis leksjon i Bootcamp i backendutvikling med Node.js på CoddyKit. Dette er leksjon 3 av 4. Du kan lese hele leksjonen gratis nedenfor – og deretter øve praktisk i nettleseren med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt. Den er en del av læringsløpet i Bootcamp i backendutvikling med Node.js, og fremdriften din synkroniseres mellom nettet og CoddyKit-appen. Kurset i Bootcamp i backendutvikling med Node.js inneholder totalt 4 leksjoner.

Hvorfor bekreftelser er viktige

Som standard må en consumer som mottar en melding fra RabbitMQ, fortelle brokeren om meldingen ble behandlet vellykket. Dette signalet kalles en acknowledgement (ack).

Hvis De aktiverer auto-ack, sletter RabbitMQ meldingen idet den leveres. Hvis workeren deretter krasjer under behandlingen, er meldingen borte for alltid. Med manual acks beholder brokeren meldingen til De bekrefter den, og leverer den på nytt hvis consumeren avsluttes.

  • auto-ack: raskt, men levering høyst én gang (meldinger kan gå tapt)
  • manual ack: trygt og gir levering minst én gang

For alle jobber som faktisk utfører arbeid (belastning av et kort, sending av e-post eller skriving til en database) bør De nesten alltid bruke manuelle ack-er.

Konsumere med manuelle ack-er

Med biblioteket amqplib sender De { noAck: false } til channel.consume for å aktivere manuell bekreftelse. Når handleren er fullført uten feil, kaller De channel.ack(msg).

Frem til De sender ack, regner RabbitMQ meldingen som unacked og leverer den på nytt hvis channel-en eller connection-en lukkes.

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

RabbitMQ gir Dem tre måter å svare på en levert melding:

  • channel.ack(msg) — vellykket, fjern meldingen
  • channel.nack(msg, false, requeue) — feil; hvis requeue=true, legges meldingen tilbake i queue-en, og hvis false, forkastes den (eller sendes til dead letter)
  • channel.reject(msg, requeue) — som nack, men bare for én enkelt melding

Det andre argumentet til nack er allUpTo. Sett det til false for å bare behandle den aktuelle meldingen; true sender nack for alle meldinger uten ack frem til denne.

Viktig valg: requeue=true prøver på nytt umiddelbart og kan føre til uendelige løkker for meldinger som alltid mislykkes. Foretrekk requeue=false kombinert 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: Kontrollere arbeid under behandling

Uten begrensninger sender RabbitMQ så mange meldinger som mulig til en konsument, og alle blir liggende ubekreftet i minnet. Det kan overbelaste en treg worker.

channel.prefetch(n) angir det maksimale antallet ubekreftede meldinger en konsument kan holde samtidig. Med manuelle bekreftelser behandler en worker med prefetch(1) én melding, bekrefter den og mottar deretter den neste — noe som gir rettferdig distribusjon med tilbakepress.

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

Hva er en giftig melding?

En giftig melding er en melding som alltid mislykkes under behandling — for eksempel ugyldig JSON, en referanse til en slettet post eller en valideringsfeil som ikke kan gjenopprettes fra. Hvis du ganske enkelt bruker nack(msg, false, true) på den, legges meldingen tilbake i køen, leveres på nytt, mislykkes igjen og havner i en evig løkke som binder opp CPU-en.

Du trenger en måte å gi opp etter noen forsøk og flytte meldingen til et trygt sted for inspeksjon. Dette stedet er en Dead-Letter Queue (DLQ).

  • Slutt å prøve på nytt etter N forsøk
  • Flytt meldingen ut av hovedflyten
  • Behold den for feilsøking eller manuell avspilling

Dead-letter-utvekslinger

RabbitMQ kan automatisk rute en melding til en annen exchange når den blir dead-lettered. En melding blir dead-lettered når:

  • den blir avvist med nack eller reject med requeue=false, eller
  • TTL-en utløper, eller
  • køen overskrider maksimal lengde

Du konfigurerer dette med køargumentene x-dead-letter-exchange (påkrevd) og eventuelt x-dead-letter-routing-key. Bind en kø til denne exchange-en, så havner døde meldinger der.

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

Sende en feil til DLQ-en

Når orders-køen er konfigurert med en dead-letter exchange, er det enkelt å sende en melding dit: Du bruker nack uten å legge meldingen tilbake i køen.

Broker-en håndterer routingen for deg — du publiserer aldri manuelt til DLQ-en. Det holder konsumentlogikken ryddig: Behandle meldingen, og la RabbitMQ sende den til dead-letter ved feil som ikke kan gjenopprettes fra.

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

Telle nye forsøk med headere

Ofte vil du prøve noen ganger på nytt før du gir opp — en midlertidig tidsavbruddsfeil mot databasen kan for eksempel lykkes på andre forsøk. RabbitMQ har ingen innebygd teller for nye forsøk, så du må spore forsøkene selv.

Når en melding blir dead-lettered, legger RabbitMQ til en x-death-headerliste som beskriver hver dead-letter-hendelse, inkludert en count. Du kan lese denne for å avgjøre om meldingen skal prøves på nytt eller rutes til den endelige DLQ-en.

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
}

Forsinkede nye forsøk med en TTL-ventekø

Umiddelbar requeue prøver på nytt med en gang, noe som er uheldig ved midlertidige feil som trenger tid til å forsvinne. Et vanlig mønster er en retry-kø med TTL som sender meldingen tilbake til hovedkøen som dead-letter.

Flyt: Hovedkøen mislykkes → meldingen går til orders.retry, som har x-message-ttl (for eksempel 5s) og en dead-letter exchange som peker tilbake til orders. Når TTL-en utløper, flytter RabbitMQ meldingen tilbake automatisk — og gir dermed en innebygd forsinkelse mellom forsøkene.

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

Slik settes logikken for nye forsøk sammen

Her er beslutningsflyten en robust konsument følger ved feil:

  • Parsing eller validering mislykkes permanent → send meldingen umiddelbart som dead-letter til parking-DLQ-en
  • Midlertidig feil og antall forsøk < maksimum → legg meldingen i retry-køen (TTL) for et forsinket nytt forsøk
  • Midlertidig feil, men antall forsøk ≥ maksimum → gi opp og send meldingen til parking-DLQ-en

Dette begrenser den totale arbeidsmengden, unngår intensive løkker og bevarer mislykkede meldinger for inspeksjon eller manuell avspilling.

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 kjørbar simulering av nye forsøk

RabbitMQ-atferden krever en broker, men logikken for å avgjøre nye forsøk er ren JavaScript som du kan analysere og teste isolert. Nedenfor finner du en selvstendig simulering av avgjørelsen mellom ack, nytt forsøk og dead-letter som en konsument av ordre tar.

Den viser hovedideen: lykkes og bekreft, prøv på nytt så lenge grensen ikke er nådd, og parker meldingen i DLQ-en når alle nye forsøk er brukt opp.

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

Kort kontroll

Du har en konsument som bruker manuelle bekreftelser. En melding inneholder ugyldig JSON som aldri kan parses. Du vil hindre at den går i en evig løkke, men samtidig beholde den for senere inspeksjon.

Oppsummering

Du har lært hvordan du kan gjøre meldingsleveringen i RabbitMQ pålitelig og robust:

  • Manuelle bekreftelser (noAck: false + channel.ack) gir levering minst én gang, slik at et krasj aldri fører til at en melding går tapt.
  • nack/reject med requeue=false sender en mislykket melding til en dead-letter exchange i stedet for at den går i en evig løkke.
  • prefetch(n) gir tilbakepress på trege workere ved å begrense antallet ubekreftede meldinger.
  • Dead-letter exchanges (x-dead-letter-exchange) ruter feil automatisk til en DLQ for inspeksjon og avspilling.
  • Nye forsøk bruker en TTL-ventekø som sender meldingen tilbake til hovedkøen som dead-letter, med en teller for antall forsøk (via x-death eller en egendefinert header) som begrenser nye forsøk før meldingen parkeres.

Tilsammen gir disse mønstrene deg begrenset, observerbar og tapsfri meldingsbehandling.

Gratis å komme i gang

Lær deg JavaScript med en AI-veileder – gratis

Skriv og kjør ekte kode i nettleseren, få umiddelbar hjelp fra en AI-veileder som er tilgjengelig døgnet rundt, og fortsett der du slapp – på nettet eller i appen.

Kurs
22
Leksjoner
92

Ofte stilte spørsmål

Er leksjonen «Kvitteringer, dead-letter-køer og retries» gratis?

Ja – hele teksten i «Kvitteringer, dead-letter-køer og retries» er gratis å lese her på nettet. For å øve interaktivt med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt, og for å låse opp resten av Bootcamp i backendutvikling med Node.js-kurset, kan du oppgradere til CoddyKit PRO. Kurset i Bootcamp i backendutvikling med Node.js inneholder totalt 4 leksjoner.

Hva lærer jeg i «Kvitteringer, dead-letter-køer og retries»?

Garanter levering med manuelle acks, håndter meldinger som ikke kan behandles, og implementer retry- og dead-letter-flyter. Du øver på Bootcamp i backendutvikling med Node.js med praktisk kode som du kjører direkte i nettleseren, mens en AI-veileder som er tilgjengelig døgnet rundt, svarer på spørsmålene dine mens du jobber deg gjennom leksjonen.

Trenger jeg erfaring for å begynne med Bootcamp i backendutvikling med Node.js?

Ingen tidligere erfaring er nødvendig. Bootcamp i backendutvikling med Node.js på CoddyKit er lagt opp for både nybegynnere og viderekomne, så De kan begynne her eller helt fra start og lære i Deres eget tempo. Dette er leksjon 3 av 4.

Hvor lang tid tar leksjonen «Kvitteringer, dead-letter-køer og retries»?

De fleste CoddyKit-leksjoner tar omtrent 5–10 minutter. Hver leksjon er kort og interaktiv, slik at De gjør jevne fremskritt og kan fortsette akkurat der De slapp – både på nettet og i appen.

Kan jeg skrive og kjøre kode i denne Bootcamp i backendutvikling med Node.js-leksjonen?

Ja. Alle Bootcamp i backendutvikling med Node.js-leksjoner har en innebygd kodeeditor, slik at De kan skrive og kjøre ekte kode direkte i nettleseren og få umiddelbar tilbakemelding fra AI – uten lokal konfigurering.

Alle leksjonene i dette kurset

  1. Produsenter, konsumenter og AMQP-modellen
  2. Exchange-typer: Direct, Topic, Fanout og Headers
  3. Kvitteringer, dead-letter-køer og retries
  4. Arbeidskøer, prefetch og konkurrerende konsumenter
← Tilbake til Bootcamp i backendutvikling med Node.js