Håndter Løpsbetingelser I NodeJS Ved Hjelp Av Mutex

Selv Om JavaScript er enkelttrådet, kan det være samtidighetsproblemer som løpsbetingelser på grunn av asynkron. Denne artikkelen tar sikte på å fortelle deg hvordan jeg håndterte dette ved hjelp av gjensidig utelukkelse. Men først, la oss huske hva som er en løpstilstand før du kommer inn i problemet og forteller deg hvordan jeg løste det.

Løpsbetingelse

en løpsbetingelse oppstår når flere prosesser prøver å få tilgang til en samme delte ressurs, og minst en av dem prøver å endre verdien. Tenk deg for eksempel at vi har en felles verdi a = 3 og to prosesser A og B. Tenk deg prosess A vil legge til 5 til den nåværende verdien av a mens prosess B vil legge til 2 til a bare hvis a < 5. Avhengig av hvilken prosess som utføres først, blir resultatet ikke det som forventes.Hvis prosess A utfører først, vil verdien av a være 8 mens den vil være 10 hvis prosess B utfører first.To unngå raseforhold, vi bruker gjensidig utelukkelse. Jeg snakker om det mer detaljert senere i denne artikkelen.

la Meg nå forklare problemet jeg hadde.

Kontekst

Et av prosjektene mine på Theodo besto i å bygge En NodeJS-applikasjon som foreslo flere arrangementer og konkurranser for sine kunder. Det var også en premium medlemskap funksjon som tillot brukere å delta i gratis arrangementer. For å delta må brukerne bare klikke på deltakelsesknappen på arrangementssiden, og de mottar en billett. Flere deltakelser til samme hendelse er ikke tillatt ved å deaktivere knappen etter deltakelse suksess.

videre er det en sikkerhetskontroll i back-end for å hindre brukere med en eksisterende betalt for å få en annen billett. Du kan se den forenklede koden 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øker jeg i databasen etter en eksisterende ordre relatert til en bruker og en hendelse. Hvis det ikke finnes noen ordre, opprettes og lagres en ny ordre i databasen. Ellers er ingenting gjort.

noen klarte imidlertid å få flere billetter ved å klikke mange ganger raskt på denne knappen. Dette var et race-condition problem.

hva var problemet nøyaktig ?

den forrige tilstanden var ikke nok. Faktisk, selv Om JavaScript er mono-threaded, forhindrer det ikke raseforhold. Når du håndterer asynkrone funksjoner, blokkerer ikke tråden kjøringen, men den utfører enten den neste linjen som ikke er avhengig av det asynkrone anropet, eller fortsetter kjøringen som svarer til en responshendelse. Som et resultat kan to forskjellige henrettelser blande seg sammen.

Ta eksemplet nedenfor: en bruker gjør 2 påfølgende forespørsler til back-end, og anta at han eller hun ikke har noen ordre som svarer til denne hendelsen. Som JavaScript er mono-threaded, vil utførelsesstakken være:

Utførelsesstack

men utførelsesrekkefølgen for de forskjellige linjene i funksjonen vil være:

Kjøring Av Kode uten lås

findOrder og createOrder er asynkrone samtaler siden de leser og skriver inn i databasen. Som en konsekvens vil de to forespørslene blande seg. Som du kan se i figuren ovenfor, blir den andre findOrder utført rett etter den første forespørselen. Derfor vil den andre evalueringen av !existOrder være true siden samtalen er gjort før du oppretter en ordre.

Konklusjon: vår bruker vil motta 2 billetter.

Løsning

jeg måtte finne en måte å låse denne delen av koden for å utføre hele funksjonen før jeg tillot en annen forespørsel om å utføre samme kode, og så unngå raseforhold. Jeg gjorde dette ved hjelp av mutex, med async-mutex-biblioteket(du kan installere det ved å kjøre yarn add async-mutex).

en mutex er et gjensidig ekskluderingsobjekt som oppretter en ressurs som kan deles mellom flere tråder i et program. Resursen kan ses som en lås som bare en tråd kan skaffe seg. Hvis en annen tråd ønsker å skaffe låsen, må den vente til låsen slippes ut. Men pass på at låsen alltid skal løses til slutt, uansett hva som skjer under utførelsen. Ellers vil det føre til vranglåser, og programmet ditt vil bli blokkert.

for ikke å bremse kjøp av billetter, brukte jeg en mutex per bruker, som jeg lagret i Et Kart. Hvis brukeren ikke er kartlagt til en mutex, opprettes en ny forekomst i kartet ved hjelp av bruker-id som en nøkkel, og så brukte jeg mutexen slik :

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-endelig blokk, sørger jeg for at låsen alltid vil bli utgitt. Nå vil utførelsen se ut som figuren nedenfor:

Kodekjøring med lås

på Denne måten vil en bruker kun kunne delta en gang.

selvfølgelig er dette ikke den eneste måten å løse denne typen problem på. Transaksjoner er også veldig nyttige i dette tilfellet.

husk også at det løste problemet på en enkelt serverforekomst. Hvis du har flere servere, bør du bruke distributed mutex for å la alle prosesser vite at låsen er anskaffet.

Legg igjen en kommentar

Din e-postadresse vil ikke bli publisert.