Handle Race Conditions In NodeJS met behulp van Mutex

zelfs als JavaScript single-threaded is, kunnen er concurrentieproblemen zijn zoals race-conditions als gevolg van asynchronisme. Dit artikel is bedoeld om u te vertellen hoe ik dit behandeld met behulp van wederzijdse uitsluiting. Maar eerst, laten we herinneren Wat is een race conditie voordat je in het probleem en vertel je hoe ik het opgelost.

racevoorwaarde

een racevoorwaarde treedt op wanneer meerdere processen proberen toegang te krijgen tot dezelfde gedeelde bron en ten minste één van hen probeert de waarde ervan te wijzigen. Stel je bijvoorbeeld voor dat we een gedeelde waarde a = 3 hebben en twee processen A en B. stel je voor dat proces a 5 wil toevoegen aan de huidige waarde van a terwijl proces B alleen 2 aan a wil toevoegen als a < 5. Afhankelijk van welk proces eerst wordt uitgevoerd, zal het resultaat niet het verwachte zijn.Als proces a eerst wordt uitgevoerd, zal de waarde van A 8 zijn, terwijl het 10 zal zijn als proces B wordt uitgevoerd first.To vermijd rassenomstandigheden, we gebruiken wederzijdse uitsluiting. Ik zal er later in dit artikel meer in detail over praten.

laat me nu uitleggen welk probleem ik had.

Context

een van mijn projecten bij Theodo bestond uit het bouwen van een NodeJS-applicatie die meerdere evenementen en wedstrijden voor zijn klanten voorstelde. Er was ook een premium lidmaatschap functie die gebruikers in staat om deel te nemen aan gratis evenementen. Om deel te nemen, Gebruikers hoeven alleen maar te klikken op de deelname knop op de evenementpagina, en ze ontvangen een ticket. Meerdere deelnames aan hetzelfde evenement worden niet toegestaan door de knop uit te schakelen na het succes van de deelname.

bovendien is er een veiligheidscontrole in de back-end om te voorkomen dat gebruikers met een bestaande betaalde bestelling een ander ticket krijgen. U kunt de vereenvoudigde code hieronder zien:

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

eerst zoek ik in de database naar een bestaande bestelling met betrekking tot een gebruiker en een gebeurtenis. Als er geen bestelling bestaat, wordt een nieuwe bestelling gemaakt en opgeslagen in de database. Anders wordt er niets gedaan.

echter, sommige mensen slaagden erin om meerdere tickets te krijgen door vele malen snel op deze knop te klikken. Dit was een race-conditie probleem.

wat was het probleem precies ?

de vorige voorwaarde was niet voldoende. Inderdaad, zelfs als JavaScript mono-threaded is, voorkomt het race-omstandigheden niet. Als je met asynchrone functies te maken hebt, blokkeert de thread de uitvoering ervan niet, maar het voert ofwel de volgende regel uit die niet afhankelijk is van de asynchrone aanroep of zet de uitvoering voort die overeenkomt met een reactiegebeurtenis. Als gevolg hiervan kunnen twee verschillende executies met elkaar verweven zijn.

neem het voorbeeld hieronder: een gebruiker doet 2 opeenvolgende verzoeken aan de back-end, en stel dat hij of zij geen volgorde heeft die overeenkomt met deze gebeurtenis. Aangezien JavaScript mono-threaded is, zal de uitvoeringstack:

Execution stack

maar de uitvoeringsvolgorde van de verschillende regels van de functie zal zijn:

Code uitvoering zonder slot

findOrder en createOrder zijn asynchrone aanroepen omdat ze in de database lezen en schrijven. Bijgevolg zullen de twee verzoeken met elkaar verweven zijn. Zoals u kunt zien in de figuur hierboven, wordt de tweede findOrder direct na de eerste aanvraag uitgevoerd. Daarom zal de tweede evaluatie van !existOrder true zijn, aangezien de oproep is gedaan voordat een order is gemaakt.

conclusie: onze gebruiker ontvangt 2 tickets.

oplossing

ik moest een manier vinden om dit deel van de code te vergrendelen om de hele functie uit te voeren alvorens een ander verzoek toe te staan om dezelfde code uit te voeren, en zo race-omstandigheden te vermijden. Ik deed dit met mutex, met de Async-mutex bibliotheek (je kunt het installeren door yarn add async-mutexuit te voeren).

een mutex is een object voor wederzijdse uitsluiting dat een bron creëert die gedeeld kan worden tussen meerdere threads van een programma. De bron kan worden gezien als een slot dat slechts één thread kan verwerven. Als een andere draad wil het slot te verwerven, moet het wachten tot het slot is vrijgegeven. Maar pas op dat het slot altijd moet worden vrijgegeven uiteindelijk, ongeacht wat er gebeurt tijdens de uitvoering. Anders, het zal leiden tot deadlocks, en uw programma zal worden geblokkeerd.

om de aankoop van tickets niet te vertragen, gebruikte ik één mutex per gebruiker, die ik op een kaart had opgeslagen. Als de gebruiker niet is toegewezen aan een mutex, wordt een nieuwe instantie gemaakt in de kaart met behulp van de gebruiker id als een sleutel, en dan heb ik de mutex gebruikt als dit :

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

je kunt zien dat met het try-catch-finally blok, ik ervoor zorg dat het slot altijd zal worden vrijgegeven. Nu, de uitvoering zal eruit zien als de figuur hieronder:

Code-uitvoering met slot

op deze manier kan een gebruiker slechts één keer deelnemen.

dit is natuurlijk niet de enige manier om dit soort problemen op te lossen. Transacties zijn ook erg nuttig in dit geval.

houd er ook rekening mee dat het probleem op een enkele Server instantie is opgelost. Als je meerdere servers hebt, dan moet je gedistribueerde mutex gebruiken om alle processen te laten weten dat het slot is verworven.

Geef een antwoord

Het e-mailadres wordt niet gepubliceerd.