Hajautetut lukot ja Redlock-algoritmi
Koordinoi yksinoikeudellista käyttöä turvallisesti instanssien välillä ja ymmärrä hajautetun lukituksen rajoitukset.
Hajautetut lukot ja Redlock-algoritmi on ilmainen Node.js-taustakehityksen bootcamp-oppitunti CoddyKitissä. Tämä on oppitunti 2/4. Voit lukea koko oppitunnin alta ilmaiseksi ja harjoitella sen jälkeen käytännössä selaimessa sisäänrakennetulla koodieditorilla ja ympäri vuorokauden käytettävissä olevan tekoälytuutorin avulla. Oppitunti kuuluu Node.js-taustakehityksen bootcamp-oppimispolkuun, ja edistymisesi synkronoituu verkon ja CoddyKit-sovelluksen välillä. Node.js-taustakehityksen bootcamp-kurssilla on yhteensä 4 oppituntia.
Miksi hajautettuja lukkoja tarvitaan?
Kun Node.js-rajapinta toimii yhtenä prosessina, yksinkertainen muistinsisäinen mutex riittää sarjoittamaan kriittiseen osaan kohdistuvan käytön. Tuotannon taustajärjestelmät toimivat kuitenkin yleensä useina instansseina kuormantasaajan takana ja usein useilla koneilla.
- Kaksi podia voi yrittää veloittaa saman laskun.
- Kaksi työntekijää voi poimia saman työn jonosta.
- Kaksi pyyntöä voi yrittää luoda saman kalliin välimuistimerkinnän uudelleen (cache stampede -ilmiö).
Muistinsisäinen lukko toimii vain yhdessä prosessissa — muut instanssit eivät tiedä siitä mitään. Kun haluatte koordinoida yksinomaista käyttöoikeutta instanssien välillä, tarvitsette lukon, joka sijaitsee jaetussa ulkoisessa tallennustilassa; Redis on tähän suosittu vaihtoehto.
Ensimmäinen (naiivi) Redis-lukko
Perusprimitiivi on atominen komento SET key value NX PX ttl. NX tarkoittaa "aseta vain, jos avainta ei ole olemassa", ja PX asettaa vanhenemisajan millisekunteina, jotta lukko vapautuu automaattisesti sen haltijan kaatuessa.
- Jos
SETpalauttaa arvonOK, lukko hankittiin onnistuneesti. - Jos se palauttaa arvon
null, joku muu pitää lukkoa hallussaan.
Arvon on oltava yksilöllinen satunnainen tunniste jokaisella hankintakerralla — tarvitsette sitä myöhemmin lukon turvalliseen vapauttamiseen.
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 */ }Turvallinen vapauttaminen: tarkista ja poista
Vapauttaminen on vaarallinen vaihe. Naiivi DEL key voi poistaa jonkun muun lukon: jos työnne ylittää TTL-ajan, lukko vanhenee, toinen instanssi hankkii sen ja myöhässä suoritettu DEL poistaa heidän lukonsa.
Ratkaisu on poistaa lukko vain, jos tallennettu arvo vastaa edelleen omaa tunnistettanne. Tarkistuksen ja poiston on oltava atominen, joten suoritamme ne Lua-skriptinä — Redis suorittaa skriptit ilman muiden komentojen väliin sekoittumista.
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: vaikein säätöpäätös
TTL on arvio siitä, kuinka kauan kriittisen osan suoritus kestää.
- Liian lyhyt TTL saa lukon vanhenemaan kesken työn, jolloin toinen työntekijä pääsee sisään — keskinäinen poissulku rikkoutuu.
- Liian pitkä TTL tarkoittaa, että haltijan kaatuessa kaikki joutuvat odottamaan koko TTL-ajan ennen kuin kukaan voi jatkaa.
Nyrkkisääntönä TTL kannattaa asettaa muutamaan kertaan kriittisen osan p99-keston suuruiseksi, suojattu työ kannattaa pitää lyhyenä ja pitkissä tehtävissä kannattaa käyttää vahtia, joka pidentää lukkoa ajoittain yhden erittäin pitkän TTL-arvon sijaan.
Lukon pidentäminen (vahtimalli)
Jos työn kesto on epävarma, hankkikaa lukko kohtuullisella TTL-arvolla ja uusikaa se ajastetusti niin kauan kuin pidätte lukkoa hallussanne. Kuten vapauttamisen myös pidentämisen on perustuttava tunnisteeseenne, jotta ette koskaan pidennä lukkoa, joka on jo siirtynyt toiselle.
Vahti suoritetaan noin kolmasosan TTL-ajasta välein, mikä antaa pelivaraa kellon heilahteluille ja GC-tauoille.
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);
}Yhden Redis-solmun ympäristö on SPOF
Tähän asti kaikessa on oletettu yksi Redis-solmu. Tämä solmu on yksittäinen vikaantumispiste, joten tiimit lisäävät replikaan perustuvan vikasietoisuuden. Redis-replikointi on kuitenkin asynkronista, mikä rikkoo huomaamatta keskinäisen poissulkemisen:
- Asiakas A hankkii lukon master-solmusta.
- Master-solmu kaatuu ennen kuin kirjoitus on replikoitu replikaan.
- Replika ylennetään master-solmuksi, eikä sillä ole tietoa lukosta.
- Asiakas B hankkii "saman" lukon uudesta master-solmusta.
Nyt kahdella asiakkaalla on lukko samanaikaisesti. Redlock-algoritmi suunniteltiin ratkaisemaan tämä vikasietoisuuden aikainen katkos.
Redlock-algoritmi
Redlock käyttää N:ää toisistaan riippumatonta Redis-master-solmua (yleensä viittä), joiden välillä ei ole replikointia. Hankkiakseen lukon asiakas:
- Tallentaa aloitusajan ja yrittää sitten suorittaa
SET NX PX-komennon samalla avaimella ja tunnisteella kaikissa N solmussa käyttäen lyhyttä solmukohtaista aikakatkaisua. - Laskee onnistumiset. Lukko katsotaan hankituksi vain, jos saavutetaan koorum (N/2 + 1 eli 3 solmua viidestä) ja kokonaiskulunut aika on TTL-arvoa lyhyempi.
- Tehollinen voimassaoloaika = TTL vähennettynä kuluneella ajalla ja kellon poikkeaman varalla.
Jos koorumia ei saavuteta (tai aika loppuu), asiakas vapauttaa lukon kaikista solmuista ja yrittää uudelleen lyhyen satunnaisen viiveen jälkeen.
redlock-kirjaston käyttö
Redlockia tarvitsee harvoin toteuttaa itse. redlock-npm-paketti ottaa vastaan taulukon toisistaan riippumattomia Redis-asiakkaita ja tarjoaa using()-funktion, joka hankkii lukon, pidentää sitä automaattisesti ja vapauttaa sen callback-funktion ympäriltä.
retryCount/retryDelaymäärittävät, kuinka pitkään lukon hankkimista yritetään ennen luovuttamista.using()antaa käyttöön signaalin — tarkistakaasignal.aborted, jotta havaitsette, jos lukko menetetään työn aikana.
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
});
}Aitaustunnisteet: todellinen turvaverkko
Martin Kleppmannin tunnettu kritiikki kuuluu näin: mikään aikakatkaisuihin perustuva lukko ei voi taata turvallisuutta, jos lukon haltija pysähtyy (GC:n tai virtuaalikoneen jumiutumisen vuoksi) TTL-arvoa pidemmäksi ajaksi. Lukko vanhenee, toinen asiakas jatkaa toimintaansa, ja pysähtynyt asiakas herää edelleen uskoen pitävänsä lukkoa.
Vankka puolustuskeino on aitaustunniste: jokaisen lukon myöntämisen yhteydessä annettava kasvava numero. Suojattu resurssi itse hylkää kirjoituksen, jonka tunniste on pienempi kuin suurin sen jo näkemä tunniste — näin vanhentunut, pysähtynyt kirjoittaja aidataan ulos kohteessa.
// 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)); // 34Lukot ja idempotenttisuus
Lukko pienentää samanaikaisen suorituksen todennäköisyyttä, mutta aikakatkaisut ja vikasietoisuustilanteet tarkoittavat, ettei siitä voi koskaan tehdä ehdotonta takuuta. Käsitelkää lukkoa optimointina, älkää viimeisenä puolustuslinjana.
- Tehkää suojatusta toiminnosta idempotentti — sen suorittaminen kahdesti tuottaa saman tuloksen.
- Käyttäkää tietokannan yksikäsitteisyysrajoitteita tai ehdollisia päivityksiä (compare-and-set), jotta kaksoiskirjoitus epäonnistuu näkyvästi.
- Käyttäkää aitaustunnisteita siellä, missä resurssi tukee niitä.
Paras käytäntö: käyttäkää lukkoa turhan työn ja kilpailutilanteiden välttämiseen, mutta suunnitelkaa järjestelmä niin, että harvinainen kaksoissuoritus on silti oikein.
Tarvitsetteko edes Redlockia?
Redlock lisää operatiivisia kustannuksia: viisi toisistaan riippumatonta Redis-asennusta, huolellinen kellojen hallinta ja uudelleenyritysten säätäminen. redis-projektin ylläpitäjät itse huomauttavat, että tehokkuutta koskevissa käyttötapauksissa (saman työn tekemisen estäminen kahdesti) yksisolmuinen lukko riittää — satunnainen kaksoissuoritus tuhlaa vain hieman työtä.
- Tehokkuuslukko (välimuistin uudelleenrakennus, deduplikointi): yksittäinen Redis-komento SET NX PX riittää.
- Oikeellisuuslukko (raha, varasto): älkää luottako yksin mihinkään aikakatkaisuun perustuvaan lukkoon — lisätkää idempotenttisuus ja aitaus Redlockista riippumatta.
Turvautukaa Redlockiin vain, kun yksisolmuisen HA-vikasietoisuuden epäonnistuminen ei aidosti ole hyväksyttävää ja resurssia ei voi aidata.
Pikatarkistus
Testatkaa ymmärryksenne hajautettujen lukkojen turvallisuudesta.
Kertaus
Opitte koordinoimaan yksinoikeudellista käyttöä Node.js-instanssien välillä — ja ymmärtämään sen rajoitukset.
- Hankkikaa lukko atomisella komennolla
SET key token NX PX ttl; tunnisteen on oltava yksilöllinen jokaisella hankintakerralla. - Vapauttakaa ja pidentäkää lukkoa vain tunnisteen tarkistavalla Lua-komentosarjalla, jotta ette koskaan käsittele jonkun muun lukkoa; uusikaa pitkäkestoisten tehtävien lukko watchdog-mekanismilla.
- TTL on kompromissi: liian lyhyt arvo rikkoo poissulkemisen, liian pitkä arvo hidastaa palautumista kaatumisen jälkeen.
- Redlock käyttää koorumia N:stä toisistaan riippumattomasta master-solmusta selviytyäkseen yksittäisen solmun vikasietotilanteesta, mutta se perustuu silti aikakatkaisuun.
- Mikään aikakatkaisuun perustuva lukko ei ole turvallinen pitkiä pysähdyksiä vastaan — lisätkää aitaustunnisteet ja idempotenttisuus oikeellisuuden kannalta kriittiseen työhön.
- Käyttäkää yksisolmuista lukkoa tehokkuuden vuoksi ja varatkaa Redlock todellisiin HA-tarpeisiin.
Opi JavaScript tekoälytuutorin avulla — ilmaiseksi
Kirjoita ja suorita oikeaa koodia selaimessa, saa välitöntä apua tekoälytuutorilta ympäri vuorokauden ja jatka siitä, mihin jäit, verkossa tai sovelluksessa.
- Kurssit
- 22
- Oppitunnit
- 92
Usein kysytyt kysymykset
Onko oppitunti ”Hajautetut lukot ja Redlock-algoritmi” ilmainen?
Kyllä – oppitunnin ”Hajautetut lukot ja Redlock-algoritmi” koko tekstin voi lukea täällä verkossa ilmaiseksi. Jos haluat harjoitella interaktiivisesti sisäänrakennetulla koodieditorilla ja ympäri vuorokauden käytettävissä olevan tekoälytuutorin avulla sekä avata koko Node.js-taustakehityksen bootcamp-kurssin, päivitä CoddyKit PROhon. Node.js-taustakehityksen bootcamp-kurssilla on yhteensä 4 oppituntia.
Mitä opin oppitunnilla ”Hajautetut lukot ja Redlock-algoritmi”?
Koordinoi yksinoikeudellista käyttöä turvallisesti instanssien välillä ja ymmärrä hajautetun lukituksen rajoitukset. Harjoittelet Node.js-taustakehityksen bootcamp-aihetta koodilla, jonka suoritat suoraan selaimessa. Ympäri vuorokauden käytettävissä oleva tekoälytuutori vastaa kysymyksiisi oppitunnin aikana.
Tarvitsenko kokemusta aloittaakseni Node.js-taustakehityksen bootcamp-opiskelun?
Aiempi kokemus ei ole tarpeen. CoddyKitin Node.js-taustakehityksen bootcamp-oppimispolku sopii vasta-alkajista edistyneisiin, joten voit aloittaa tästä tai alusta ja edetä omaan tahtiisi. Tämä on oppitunti 2/4.
Kuinka kauan ”Hajautetut lukot ja Redlock-algoritmi”-oppitunnin suorittaminen kestää?
Useimmat CoddyKitin oppitunnit kestävät noin 5–10 minuuttia. Jokainen oppitunti on lyhyt ja interaktiivinen, joten edistyt tasaisesti ja voit jatkaa siitä, mihin jäit – sekä verkossa että sovelluksessa.
Voinko kirjoittaa ja suorittaa koodia tällä Node.js-taustakehityksen bootcamp-oppitunnilla?
Kyllä. Jokainen Node.js-taustakehityksen bootcamp-oppitunti sisältää sisäänrakennetun koodieditorin, joten voit kirjoittaa ja suorittaa oikeaa koodia suoraan selaimessa ja saada välitöntä palautetta tekoälyltä – paikallista asennusta ei tarvita.
Kaikki tämän kurssin oppitunnit
- Cache-aside-, write-through- ja TTL-strategiat
- Hajautetut lukot ja Redlock-algoritmi
- Pub/Sub, Streams ja nopeusrajoitus Redisillä
- Cache stampede- ja thundering herd -ilmiöiden estäminen