Bootcamp i backendutvikling med Node.js · leksjon

Distribuerte låser og Redlock-algoritmen

Samordne eksklusiv tilgang på tvers av instanser på en trygg måte, og forstå begrensningene ved distribuert låsing.

Leksjon 2 av 413 trinn

Distribuerte låser og Redlock-algoritmen er en gratis leksjon i Bootcamp i backendutvikling med Node.js på CoddyKit. Dette er leksjon 2 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 distribuerte låser?

Når Node.js-API-et kjører som én enkeltprosess, er en enkel mutex i minnet nok til å serialisere tilgangen til en kritisk seksjon. Produksjonsbakender kjører imidlertid med mange instanser bak en lastbalanserer, ofte på flere maskiner.

  • To pods kan forsøke å belaste den samme fakturaen.
  • To arbeidere kan hente den samme jobben fra en kø.
  • To forespørsler kan forsøke å generere den samme kostbare cache-oppføringen på nytt (en cache-stampede).

En lås i minnet finnes bare i én prosess – de andre instansene vet ingenting om den. For å koordinere eksklusiv tilgang på tvers av instanser trenger De en lås som ligger i felles ekstern lagring, og Redis er et populært sted å lagre den.

En første (naiv) Redis-lås

Den grunnleggende primitiven er den atomiske kommandoen SET key value NX PX ttl. NX betyr «angir bare verdien hvis nøkkelen ikke finnes», og PX angir utløp i millisekunder, slik at låsen frigjøres automatisk hvis innehaveren krasjer.

  • Hvis SET returnerer OK, har De fått låsen.
  • Hvis den returnerer null, har noen andre låsen.

Verdien må være et unikt tilfeldig token for hver gang låsen hentes – De trenger det senere for å frigjøre låsen på en trygg måte.

const { createClient } = require('redis');
const crypto = require('crypto');

async function acquire(redis, key, ttlMs) {
  const token = crypto.randomUUID();
  // NX = set only if absent, PX = expiry in ms
  const ok = await redis.set(key, token, { NX: true, PX: ttlMs });
  return ok === 'OK' ? token : null;
}

// Usage sketch (needs a running Redis):
// const redis = createClient(); await redis.connect();
// const token = await acquire(redis, 'lock:invoice:42', 10000);
// if (token) { /* do exclusive work */ }

Trygg frigjøring: Kontroller og slett

Frigjøringen er den farlige delen. En naiv DEL key kan slette en annens lås: Hvis arbeidet Deres varer lenger enn TTL-en, utløper låsen, en annen instans får den, og den sene DEL-kommandoen Deres sletter låsen deres.

Løsningen er å bare slette hvis den lagrede verdien fortsatt er lik Deres token. Kontrollen og slettingen må være atomiske, så vi kjører dem som et Lua-skript – Redis kjører skript uten å flette inn andre kommandoer.

const RELEASE_LUA = `
if redis.call('get', KEYS[1]) == ARGV[1] then
  return redis.call('del', KEYS[1])
else
  return 0
end`;

async function release(redis, key, token) {
  // Returns 1 if we owned and removed it, 0 otherwise
  return redis.eval(RELEASE_LUA, { keys: [key], arguments: [token] });
}

TTL: Den vanskeligste innstillingsbeslutningen

TTL-en er et anslag på hvor lenge den kritiske seksjonen tar.

  • For kort, og låsen utløper mens arbeidet pågår, slik at en annen arbeider slipper til – den gjensidige utelukkelsen brytes.
  • For lang, og hvis en innehaver krasjer, må alle vente gjennom hele TTL-perioden før noen kan fortsette.

Tommelfingerregler: Sett TTL-en til noen ganger varigheten til den kritiske seksjonen ved p99, hold det beskyttede arbeidet kort, og bruk ved lange oppgaver en watchdog som forlenger låsen med jevne mellomrom i stedet for én svært lang TTL.

Forlenge en lås (watchdog-mønsteret)

For arbeid med usikker varighet henter De en moderat TTL og fornyer den med et tidsintervall så lenge De fortsatt har låsen. På samme måte som ved frigjøring må forlengelsen beskyttes av Deres token, slik at De aldri forlenger en lås som allerede har gått videre til noen andre.

Watchdog-en kjører omtrent ved en tredjedel av TTL-en, noe som gir en sikkerhetsmargin mot klokkevariasjoner og GC-pauser.

const EXTEND_LUA = `
if redis.call('get', KEYS[1]) == ARGV[1] then
  return redis.call('pexpire', KEYS[1], ARGV[2])
else
  return 0
end`;

function startWatchdog(redis, key, token, ttlMs) {
  const timer = setInterval(async () => {
    const ok = await redis.eval(EXTEND_LUA, {
      keys: [key], arguments: [token, String(ttlMs)],
    });
    if (ok !== 1) clearInterval(timer); // lost the lock; stop renewing
  }, Math.floor(ttlMs / 3));
  return () => clearInterval(timer);
}

Redis på én node er et enkelt feilpunkt

Alt så langt forutsetter én Redis-node. Denne noden er et enkelt feilsted, så team legger til en replika med failover. Men Redis-replikering er asynkron, og det bryter i praksis gjensidig utelukkelse:

  • Klient A tilegner seg låsen på masteren.
  • Masteren krasjer før skrivingen replikeres til replikaen.
  • Replikaen promoteres, men har ingen registrering av låsen.
  • Klient B tilegner seg «den samme» låsen på den nye masteren.

Nå har to klienter låsen samtidig. Algoritmen Redlock ble utviklet for å håndtere dette failover-vinduet.

Redlock-algoritmen

Redlock bruker N uavhengige Redis-mastere (vanligvis 5), uten replikering mellom dem. For å tilegne seg en lås gjør en klient følgende:

  • Registrerer starttidspunktet og prøver deretter å kjøre SET NX PX med samme nøkkel og token på alle N nodene, med en kort tidsavbruddsgrense per node.
  • Teller hvor mange forsøk som lykkes. Låsen regnes som tilegnet bare hvis klienten fikk et flertall (N/2 + 1, altså 3 av 5) og den totale medgåtte tiden er kortere enn TTL-en.
  • Den effektive gyldigheten = TTL minus medgått tid minus et tillegg for klokkedrift.

Hvis klienten ikke oppnår flertall (eller går tom for tid), frigir den låsen på alle noder og prøver på nytt etter en kort, tilfeldig forsinkelse.

Bruke redlock-biblioteket

De færreste implementerer Redlock manuelt. npm-pakken redlock tar imot en rekke uavhengige Redis-klienter og tilbyr using(), som tilegner seg, forlenger automatisk og frigir låsen rundt tilbakekallet deres.

  • retryCount / retryDelay styrer hvor mange forsøk som gjøres før pakken gir opp.
  • using() gir Dem et signal — sjekk signal.aborted for å oppdage at låsen gikk tapt under arbeidet.
const Client = require('ioredis');
const Redlock = require('redlock').default;

const nodes = [
  new Client({ host: 'redis-a' }),
  new Client({ host: 'redis-b' }),
  new Client({ host: 'redis-c' }),
];

const redlock = new Redlock(nodes, {
  retryCount: 10,
  retryDelay: 200,   // ms between attempts
  driftFactor: 0.01, // clock-drift allowance
});

async function chargeInvoice(id) {
  await redlock.using([`lock:invoice:${id}`], 5000, async (signal) => {
    await doCharge(id);
    if (signal.aborted) throw signal.error; // lost the lock
  });
}

Fencing-tokens: det egentlige sikkerhetsnettet

Martin Kleppmanns velkjente kritikk er at ingen lås basert på tidsavbrudd kan garantere sikkerhet hvis en låseier settes på pause (GC, VM-stans) lenger enn TTL-en. Låsen utløper, en annen klient fortsetter, og den pausede klienten våkner fortsatt i troen på at den har låsen.

Det robuste forsvaret er et fencing-token: et monotonisk økende tall som utstedes hver gang en lås tildeles. Den beskyttede ressursen selv avviser alle skrivinger med et token som er lavere enn det høyeste den allerede har sett — dermed stenges en foreldet, pausert skriver ute ved målet.

// Resource-side guard: reject writes with a stale fencing token.
function makeFencedStore() {
  let highestSeen = 0;
  const data = {};
  return {
    write(key, value, token) {
      if (token <= highestSeen) {
        throw new Error(`fenced: token ${token} <= ${highestSeen}`);
      }
      highestSeen = token;
      data[key] = value;
      return token;
    },
  };
}

const store = makeFencedStore();
store.write('balance', 100, 33);     // ok, token 33
try {
  store.write('balance', 999, 32);   // stale writer, fenced out
} catch (e) {
  console.log(e.message);            // fenced: token 32 <= 33
}
console.log('stored:', store.write('balance', 200, 34)); // 34

Låser kontra idempotens

En lås reduserer sannsynligheten for samtidig kjøring, men tidsavbrudd og failover betyr at De aldri kan gjøre dette til en absolutt garanti. Betrakt låsen som en optimalisering, ikke som siste forsvarslinje.

  • Gjør den beskyttede operasjonen idempotent — hvis den kjøres to ganger, blir resultatet det samme.
  • Bruk databasens unikhetsbegrensninger eller betingede oppdateringer (compare-and-set), slik at en duplikatskriving feiler tydelig.
  • Bruk fencing-tokens der ressursen støtter dette.

Beste praksis er å bruke lås for å unngå bortkastet arbeid og konkurranse, men utforme systemet slik at en sjelden dobbel kjøring fortsatt er korrekt.

Trenger De egentlig Redlock?

Redlock medfører driftskostnader: fem uavhengige Redis-distribusjoner, nøye klokkehåndtering og justering av nytt forsøk. Vedlikeholderne av redis påpeker selv at en enkeltnode-lås er tilstrekkelig for brukstilfeller som gjelder effektivitet (unngå å utføre det samme arbeidet to ganger) — en sporadisk dobbel kjøring koster bare litt ekstra arbeid.

  • Effektivitetslås (gjenoppbygging av hurtigbuffer, deduplisering): én Redis-kommando med SET NX PX er nok.
  • Korrekthetslås (penger, lagerbeholdning): stol ikke bare på en tidsavbruddsbasert lås — legg til idempotens og fencing, uavhengig av Redlock.

Bruk Redlock bare når failover med HA for én node faktisk er uakseptabelt, og De ikke kan bruke fencing ved ressursen.

Hurtigsjekk

Test forståelsen Deres av sikkerhet ved distribuert låsing.

Oppsummering

De har lært hvordan De koordinerer eksklusiv tilgang på tvers av Node.js-instanser — og hvor grensene går.

  • Tilegn låsen med atomisk SET key token NX PX ttl; tokenet må være unikt for hver tilegnelse.
  • Frigi og forleng bare via et tokenkontrollert Lua-skript, slik at De aldri berører andres lås; forny lange oppgaver med en vakthund.
  • TTL er et kompromiss: for kort TTL bryter den eksklusive tilgangen, mens for lang TTL forsinker gjenoppretting etter en krasj.
  • Redlock bruker flertall på tvers av N uavhengige mastere for å tåle failover av én node, men er fortsatt basert på tidsavbrudd.
  • Ingen tidsavbruddsbasert lås er sikker mot lange pauser — legg til fencing-tokens og idempotens for arbeid der korrekthet er kritisk.
  • Bruk en enkeltnode-lås for effektivitet, og reserver Redlock for reelle HA-behov.
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 «Distribuerte låser og Redlock-algoritmen» gratis?

Ja – hele teksten i «Distribuerte låser og Redlock-algoritmen» 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 «Distribuerte låser og Redlock-algoritmen»?

Samordne eksklusiv tilgang på tvers av instanser på en trygg måte, og forstå begrensningene ved distribuert låsing. 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 2 av 4.

Hvor lang tid tar leksjonen «Distribuerte låser og Redlock-algoritmen»?

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. Cache-aside, write-through og TTL-strategier
  2. Distribuerte låser og Redlock-algoritmen
  3. Pub/Sub, streamer og ratebegrensning med Redis
  4. Forebygging av cache-stampeder og «thundering herds»
← Tilbake til Bootcamp i backendutvikling med Node.js