Håndter løbsbetingelser i NodeJS ved hjælp af Muteks

selvom JavaScript er enkelttrådet, kan der være samtidighedsproblemer som race-forhold på grund af asynkronisme. Denne artikel har til formål at fortælle dig, hvordan jeg håndterede dette ved hjælp af gensidig udelukkelse. Men lad os først huske, hvad der er en race-tilstand, før vi går ind i problemet og fortæller dig, hvordan jeg løste det.

Race condition

en race condition opstår, når flere processer forsøger at få adgang til en samme delt ressource, og mindst en af dem forsøger at ændre dens værdi. Forestil dig for eksempel, at vi har en delt værdi a = 3 og to processer A og B. Forestil dig, at proces A vil tilføje 5 til den aktuelle værdi af A, mens proces B kun vil tilføje 2 til A, hvis a < 5. Afhængigt af hvilken proces der udføres først, vil resultatet ikke være det forventede.Hvis proces a udføres først, vil værdien af A være 8, mens den vil være 10, Hvis proces B udføres first.To undgå race betingelser, vi bruger gensidig udelukkelse. Jeg vil tale om det mere detaljeret senere i denne artikel.

lad mig nu forklare det problem, jeg havde.

kontekst

et af mit projekt på Theodo bestod i at opbygge en NodeJS-applikation, der foreslog flere begivenheder og konkurrencer for sine kunder. Der var også en premium-medlemsfunktion, der gjorde det muligt for brugerne at deltage i gratis begivenheder. For at deltage skal brugerne bare klikke på deltagelsesknappen på begivenhedssiden, og de modtager en billet. Flere deltagelser til den samme begivenhed er ikke tilladt ved at deaktivere knappen efter deltagelsen succes.

derudover er der en sikkerhedskontrol i back-end for at forhindre brugere med en eksisterende betalt ordre at få en anden billet. Du kan se den forenklede kode nedenfor:

async function participateInFreeEvent(user: User, eventId: number): Promise<void> {const existOrder = await findOrder(eventId, user.id); if (!existOrder) { const order = buildNewOrder(eventId, user.id); createOrder(order.id, eventId, user.id); }}

først søger jeg i databasen efter en eksisterende ordre relateret til en bruger og en begivenhed. Hvis der ikke findes nogen ordre, oprettes og gemmes en ny ordre i databasen. Ellers er der ikke gjort noget.

men nogle mennesker formåede at få flere billetter ved at klikke mange gange hurtigt på denne knap. Dette var et race-tilstand problem.

hvad var problemet nøjagtigt ?

den tidligere betingelse var ikke nok. Faktisk, selvom JavaScript er mono-threaded, forhindrer det ikke race-betingelser. Når du beskæftiger dig med asynkrone funktioner, blokerer tråden ikke dens udførelse, men den udfører enten den næste linje, der ikke afhænger af det asynkrone opkald, eller fortsætter den udførelse, der svarer til en svarhændelse. Som et resultat kan to forskellige henrettelser flette sammen.

Tag eksemplet nedenfor: en bruger fremsætter 2 på hinanden følgende anmodninger til back-end og antager, at han eller hun ikke har nogen ordre svarende til denne begivenhed. Da JavaScript er mono-gevind, vil eksekveringsstakken være:

Eksekveringsstak

men udførelsesrækkefølgen for de forskellige linjer i funktionen vil være:

kode udførelse uden lås

findOrder og createOrder er asynkrone opkald, da de læser og skriver ind i databasen. Som følge heraf vil de to anmodninger flette sammen. Som du kan se i figuren ovenfor, udføres den anden findOrder lige efter den første anmodning. Derfor vil den anden evaluering af !existOrder være true, da opkaldet er foretaget, før du opretter en ordre.

konklusion: vores bruger modtager 2 billetter.

løsning

jeg var nødt til at finde en måde at låse denne del af koden for at udføre hele funktionen, før jeg tillod en anden anmodning om at udføre den samme kode, og så undgå race-betingelser. Jeg gjorde dette ved hjælp af muteks, med async-muteks biblioteket (du kan installere det ved at køre yarn add async-mutex).

en muteks er en gensidig udelukkelse objekt, som skaber en ressource, der kan deles mellem flere tråde af et program. Ressourcen kan ses som en lås, som kun en tråd kan erhverve. Hvis en anden tråd ønsker at erhverve låsen, skal den vente, indtil låsen frigives. Men pas på, at låsen altid skal frigives til sidst, uanset hvad der sker under udførelsen. Ellers vil det føre til deadlocks, og dit program vil blive blokeret.

for ikke at bremse køb af billetter brugte jeg en muteks pr. Hvis brugeren ikke er kortlagt til en muteks, oprettes en ny forekomst på kortet ved hjælp af Bruger-id ‘ et som en nøgle, og så brugte jeg mutekset som dette :

import { Mutex, MutexInterface } from 'async-mutex';class PaymentService {private locks : Map<string, MutexInterface>;constructor() {this.locks = new Map();}public async participateInFreeEvent(user: User, eventId: number): Promise<void> { if (!this.locks.has(user.id)) { this.locks.set(user.id, new Mutex()); } this.locks .get(user.id) .acquire() .then(async (release) => { try { const existOrder = await findOrder(eventId, user.id); if (!existOrder) { const order = buildNewOrder(eventId, user.id); createOrder(order.id, eventId, user.id); } } catch (error) { } finally { release(); } }, ); }}

du kan se, at med try-catch-finally-blokken sikrer jeg, at låsen altid frigives. Nu vil udførelsen se ud som figuren nedenfor:

kodekørsel med lås

på denne måde kan en bruger kun deltage en gang.

det er selvfølgelig ikke den eneste måde at løse denne slags problem på. Transaktioner er også virkelig nyttige i dette tilfælde.

husk også, at det løste problemet på en enkelt serverinstans. Hvis du har flere servere, skal du bruge distribueret muteks til at lade alle processer vide, at låsen er erhvervet.

Skriv et svar

Din e-mailadresse vil ikke blive publiceret.