käsittele Rotuolosuhteita Nodejsissa käyttäen Mutexia

vaikka JavaScript olisi yksikierteinen, voi asynkronisuudesta aiheutua samanaikaisuusongelmia, kuten rotuolosuhteita. Tämän artikkelin tarkoituksena on kertoa, miten käsittelin tätä käyttämällä molemminpuolista poissulkemista. Mutta ensin muistellaan, mikä on kisakunto ennen kuin ryhdytään ongelmaan ja kerrotaan, miten Ratkaisin sen.

kilpailutilanne

kilpailutilanne syntyy, kun useat prosessit yrittävät käyttää samaa jaettua resurssia ja ainakin yksi niistä yrittää muuttaa sen arvoa. Esimerkiksi kuvitelkaa, että meillä on yhteinen arvo a = 3 ja kaksi prosessia A ja B. kuvitelkaa prosessi a haluaa lisätä 5 A: n nykyiseen arvoon, kun taas prosessi B haluaa lisätä 2 A: han vain, jos a < 5. Riippuen siitä, mikä prosessi suorittaa ensin, tulos ei ole odotettu.Jos prosessi a suoritetaan ensin, A: n arvo on 8, kun taas se on 10, jos prosessi B suoritetaan ensin first.To vältä kisaolosuhteita, käytämme molemminpuolista poissulkemista. Puhun siitä tarkemmin myöhemmin tässä artikkelissa.

nyt selitän, mistä oli kysymys.

Context

yksi projektistani Theodossa koostui nodejs-sovelluksen rakentamisesta, joka ehdotti asiakkailleen useita tapahtumia ja kilpailuja. Pelissä oli myös premium-jäsenyyden ominaisuus, joka mahdollisti käyttäjien osallistumisen ilmaisiin tapahtumiin. Osallistuakseen, käyttäjien tarvitsee vain klikata osallistumispainiketta tapahtumasivulla, ja he saavat lipun. Useita osallistumisia samaan tapahtumaan kielletään poistamalla painike osallistumisen onnistumisen jälkeen.

lisäksi takahuoneessa on turvatarkastus, jotta käyttäjät, joilla on jo maksettu tilaus, eivät saisi toista lippua. Näet yksinkertaistetun koodin alla:

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

ensin etsin tietokannasta olemassa olevaa tilausta, joka liittyy käyttäjään ja tapahtumaan. Jos järjestystä ei ole, luodaan uusi järjestys ja tallennetaan tietokantaan. Muuten mitään ei tehdä.

jotkut onnistuivat kuitenkin saamaan useita lippuja klikkaamalla tätä painiketta monta kertaa nopeasti. Tämä oli kisakuntokysymys.

mikä oli ongelma ?

aiempi kunto ei riittänyt. Itse asiassa, vaikka JavaScript on monisäikeinen, se ei estä rodun olosuhteet. Kun käsittelet asynkronisia toimintoja, säie ei estä sen suoritusta, mutta se joko suorittaa seuraavan rivin, joka ei riipu asynkronisesta kutsusta tai jatkaa suoritusta, joka vastaa vastaustapahtumaa. Tämän seurauksena kaksi eri teloitusta voivat kietoutua toisiinsa.

ota alla oleva esimerkki: käyttäjä tekee 2 peräkkäistä pyyntöä back-Endiin, ja oletetaan, ettei hänellä ole tätä tapahtumaa vastaavaa järjestystä. Koska JavaScript on monisäikeinen, suorituspino on:

Suorituspino

, mutta funktion eri rivien suoritusjärjestys on:

koodin suoritus ilman lukkoa

findOrder ja createOrder ovat asynkronisia kutsuja, koska ne luetaan ja kirjoitetaan tietokantaan. Tämän seurauksena nämä kaksi pyyntöä kietoutuvat toisiinsa. Kuten yllä olevasta kuvasta näkyy, toinen findOrder suoritetaan heti ensimmäisen pyynnön jälkeen. Näin ollen !existOrder: n toinen arviointi on true, koska puhelu on tehty ennen tilauksen tekemistä.

johtopäätös: käyttäjämme saa 2 lippua.

ratkaisu

minun oli löydettävä tapa lukita tämä osa koodista koko toiminnon suorittamiseksi ennen kuin annoin toisen pyynnön suorittaa saman koodin, ja näin vältyin kilpailuolosuhteilta. Tein tämän käyttämällä mutexia, jossa on async-mutex-kirjasto (voit asentaa sen ajamalla yarn add async-mutex).

mutex on molemminpuolinen poissulkemisobjekti, joka luo resurssin, joka voidaan jakaa ohjelman useiden säikeiden kesken. Voimavara voidaan nähdä lukkona, jonka vain yksi säie voi hankkia. Jos toinen säie haluaa hankkia lukon, se joutuu odottamaan, kunnes lukko vapautuu. Mutta varokaa, että lukko on aina vapautettava lopulta, riippumatta siitä, mitä tapahtuu suorittamisen aikana. Muuten, se johtaa umpikujaan, ja ohjelma on estetty.

jotta lippujen ostaminen ei hidastuisi, käytin yhtä mutexia käyttäjää kohti, jonka tallensin karttaan. Jos käyttäjää ei ole kartoitettu mutexiin, kartalle luodaan uusi esiintymä käyttäen käyttäjätunnusta avaimena, ja sitten käytin mutexia näin :

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

try-catch-finally blokin avulla varmistan, että lukko irtoaa aina. Nyt toteutus näyttää alla olevalta kuviolta:

koodin suoritus lukituksella

näin käyttäjä voi osallistua vain kerran.

tämä ei tietenkään ole ainoa tapa ratkaista tällainen ongelma. Liiketoimet ovat myös todella hyödyllisiä tässä tapauksessa.

pidä myös mielessä, että se ratkaisi ongelman yhdellä palvelimella. Jos sinulla on useita palvelimia, käytä hajautettua mutexia, jotta kaikki prosessit tietävät, että lukko on hankittu.

Vastaa

Sähköpostiosoitettasi ei julkaista.