kezelje a versenyfeltételeket a nodejs-ben a Mutex használatával

még akkor is, ha a JavaScript egyszálú, az aszinkronizmus miatt előfordulhatnak párhuzamossági problémák, mint például a versenyfeltételek. Ez a cikk célja, hogy elmondja, hogyan kezeltem ezt a kölcsönös kizárás. De először idézzük fel, mi a versenyfeltétel, mielőtt belemennénk a problémába, és elmondanám, hogyan oldottam meg.

versenyfeltétel

versenyfeltétel akkor fordul elő, amikor több folyamat próbál hozzáférni egy megosztott erőforráshoz, és legalább egyikük megpróbálja módosítani annak értékét. Képzelje el például, hogy van egy közös értékünk a = 3 és két folyamat A és B. képzelje el, hogy az a folyamat 5-öt akar hozzáadni az A aktuális értékéhez, míg a B folyamat csak 2-t akar hozzáadni a-hoz, ha a < 5. Attól függően, hogy melyik folyamatot hajtja végre először, az eredmény nem lesz a várt.Ha az a folyamat hajt végre először, az a értéke 8 lesz, míg 10 lesz, ha a B folyamat végrehajtja first.To kerülje a verseny feltételeit, kölcsönös kizárást alkalmazunk. A cikk későbbi részében részletesebben beszélek róla.

most hadd magyarázzam el a problémát.

Context

az egyik projektem a Theodo-nál egy NodeJS alkalmazás felépítéséből állt, amely több eseményt és versenyt javasolt az ügyfelek számára. Volt egy prémium tagsági funkció is, amely lehetővé tette a felhasználók számára, hogy ingyenes eseményeken vegyenek részt. A részvételhez a felhasználóknak csak a részvétel gombra kell kattintaniuk az esemény oldalán, és jegyet kapnak. Ugyanazon esemény több részvétele nem engedélyezett, ha letiltja a gombot a részvétel sikere után.

ezenkívül van egy biztonsági ellenőrzés a háttérben, hogy megakadályozzák a meglévő fizetett megrendeléssel rendelkező felhasználókat, hogy újabb jegyet kapjanak. Az egyszerűsített kódot alább láthatja:

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

először az adatbázisban keresek egy meglévő megrendelést, amely egy felhasználóhoz és egy eseményhez kapcsolódik. Ha nincs megrendelés, akkor egy új megrendelés jön létre és mentésre kerül az adatbázisban. Ellenkező esetben semmi sem történik.

néhány embernek azonban sikerült több jegyet szereznie, ha sokszor gyorsan rákattintott erre a gombra. Ez egy versenyfeltétel kérdése volt.

mi volt a probléma pontosan ?

az előző feltétel nem volt elég. Valóban, még akkor is, ha a JavaScript egyszálú, nem akadályozza meg a versenyfeltételeket. Amikor aszinkron függvényekkel foglalkozik, a szál nem blokkolja a végrehajtását, de vagy végrehajtja a következő sort, amely nem függ az aszinkron hívástól, vagy folytatja a válaszeseménynek megfelelő végrehajtást. Ennek eredményeként két különböző kivégzés összefonódhat.

Vegyük az alábbi példát: egy felhasználó 2 egymást követő kérést küld a háttérnek, és tegyük fel, hogy nincs ennek az eseménynek megfelelő sorrendje. Mivel a JavaScript egyszálú, a végrehajtási verem a következő lesz:

végrehajtási verem

de a függvény különböző sorainak végrehajtási sorrendje a következő lesz:

kódfuttatás zárolás nélkül

findOrder és a createOrder aszinkron hívások, mivel olvasnak és írnak az adatbázisba. Ennek következtében a két kérés összefonódik. Amint a fenti ábrán látható, a második findOrder közvetlenül az első kérés után kerül végrehajtásra. Ezért a !existOrder második értékelése true lesz, mivel a hívás a megrendelés létrehozása előtt történt.

következtetés: felhasználónk 2 jegyet kap.

megoldás

meg kellett találnom a kód ezen részének lezárásának módját, hogy végrehajtsam az egész funkciót, mielőtt engedélyeznék egy másik kérést ugyanazon kód végrehajtására, és így elkerülhetem a versenyfeltételeket. Ezt a mutex használatával tettem, az async-mutex könyvtárral (yarn add async-mutexfuttatásával telepítheti).

a mutex egy kölcsönös kizárási objektum, amely olyan erőforrást hoz létre, amely megosztható a program több szála között. Az erőforrás olyan zárnak tekinthető, amelyet csak egy szál szerezhet be. Ha egy másik szál meg akarja szerezni a zárat, akkor meg kell várnia, amíg a zár kioldódik. De vigyázz, hogy a zárat végül mindig ki kell engedni, függetlenül attól, hogy mi történik a végrehajtás során. Ellenkező esetben holtponthoz vezet, és a program blokkolva lesz.

annak érdekében, hogy ne lassítsák le a jegyvásárlást, felhasználónként egy mutexet használtam, amelyet térképen tároltam. Ha a felhasználó nincs hozzárendelve mutexhez, akkor egy új példány jön létre a térképen a felhasználói azonosító segítségével kulcsként, majd így használtam a mutexet :

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

láthatja, hogy a try-catch-finally blokkkal biztosítom, hogy a zár mindig felszabaduljon. Most a végrehajtás az alábbi ábrán fog kinézni:

kódfuttatás zárral

ily módon a felhasználó csak egyszer vehet részt.

természetesen ez nem az egyetlen módja ennek a problémának a megoldására. A tranzakciók ebben az esetben is nagyon hasznosak.

ne feledje továbbá, hogy egyetlen szerverpéldányon oldotta meg a problémát. Ha több kiszolgálója van, akkor az elosztott mutex-et kell használnia, hogy minden folyamat tudja, hogy a zár megszerzett.

Vélemény, hozzászólás?

Az e-mail-címet nem tesszük közzé.