뮤텍스

을 사용하여 노드에서 경쟁 조건을 처리 자바 스크립트가 단일 스레드 인 경우에도 비동기성으로 인해 경쟁 조건과 같은 동시성 문제가있을 수 있습니다. 이 기사는 상호 배제를 사용하여 어떻게 처리했는지 알려주는 것을 목표로합니다. 하지만 먼저,문제에 들어가기 전에 경쟁 조건이 무엇인지 기억하고 내가 어떻게 해결했는지 알려 드리겠습니다.

경쟁 조건

경쟁 조건은 여러 프로세스가 동일한 공유 리소스에 액세스하려고 시도하고 그 중 하나 이상이 해당 값을 수정하려고 할 때 발생합니다. 예를 들어,공유 값이 있다고 상상해보십시오=3 과 두 개의 프로세스 ㅏ 과 비.상상 프로세스 ㅏ 현재 값에 5 를 더하고 싶은 반면 프로세스 비<5 인 경우에만 2 를 더하고 싶습니다. 어떤 프로세스가 먼저 실행되는지에 따라 결과가 예상되지 않습니다.프로세스가 처음 실행되면 값은 8 인 반면 프로세스는 10 인 경우 10 이됩니다.first.To 경쟁 조건을 피하십시오,우리는 상호 배제를 사용합니다. 나는이 문서의 뒷부분에서 더 자세히 그것에 대해 이야기 할 것이다.

이제 내가 가진 문제를 설명하겠습니다.

컨텍스트

테오도에서의 제 프로젝트 중 하나는 고객을 위해 여러 이벤트 및 콘테스트를 제안하는 노드 즈 응용 프로그램을 구축하는 것으로 구성되었습니다. 또한 사용자가 무료 이벤트에 참여할 수있는 프리미엄 회원 기능이있었습니다. 참여하려면,사용자는 이벤트 페이지에서 참여 버튼을 클릭해야,그들은 티켓을받을 수 있습니다. 참여 성공 후 버튼을 비활성화하면 동일한 이벤트에 대한 다중 참여가 허용되지 않습니다.

또한,다른 티켓을 얻기 위해 기존의 유료 주문과 사용자를 방지하기 위해 백엔드에 보안 검사가있다. 아래의 단순화 된 코드를 볼 수 있습니다:

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

먼저 사용자 및 이벤트와 관련된 기존 주문을 데이터베이스에서 검색합니다. 순서가 없으면 새 순서가 생성되어 데이터베이스에 저장됩니다. 그렇지 않으면 아무 것도 수행되지 않습니다.

그러나 일부 사람들은이 버튼을 여러 번 빠르게 클릭하여 여러 티켓을 얻을 수있었습니다. 이것은 경쟁 조건 문제였습니다.

문제는 정확히 무엇 이었습니까?

이전 조건이 충분하지 않았습니다. 실제로 자바 스크립트가 모노 스레드 일지라도 경쟁 조건을 막지는 못합니다. 비동기 함수를 처리할 때 스레드는 실행을 차단하지 않지만 비동기 호출에 의존하지 않는 다음 줄을 실행하거나 응답 이벤트에 해당하는 실행을 계속합니다. 결과적으로 두 개의 다른 실행이 얽힐 수 있습니다.

아래의 예를 들어 사용자가 백엔드에 2 개의 연속 요청을 하고 이 이벤트에 해당하는 순서가 없다고 가정합니다. 자바 스크립트는 모노 스레드,실행 스택 될 것입니다:

실행 스택

그러나 함수의 다른 라인의 실행 순서는 다음과 같습니다:

잠금 없이 코드 실행

findOrder 그리고createOrder은 데이터베이스에 읽고 쓰기 때문에 비동기 호출입니다. 결과적으로 두 요청이 서로 얽히게됩니다. 위의 그림에서 볼 수 있듯이 두 번째findOrder는 첫 번째 요청 중 하나 직후에 실행됩니다. 따라서!existOrder의 두 번째 평가는 주문을 만들기 전에 호출이 이루어 졌기 때문에true가됩니다.

결론:사용자는 2 장의 티켓을 받게됩니다.

솔루션

다른 요청이 동일한 코드를 실행하도록 허용하기 전에 전체 함수를 실행하기 위해 코드의이 부분을 잠그는 방법을 찾아야하므로 경쟁 조건을 피할 수있었습니다. 비동기 뮤텍스 라이브러리를 사용하여 뮤텍스를 사용하여이 작업을 수행했습니다(yarn add async-mutex을 실행하여 설치할 수 있음).

뮤텍스는 프로그램의 여러 스레드간에 공유 할 수있는 리소스를 만드는 상호 제외 객체입니다. 리소스는 하나의 스레드만 획득할 수 있는 잠금으로 볼 수 있습니다. 다른 스레드가 잠금을 획득하려는 경우 잠금이 해제될 때까지 기다려야 합니다. 그러나 실행 중에 어떤 일이 발생하든 잠금은 항상 결국 해제되어야합니다. 그렇지 않으면 교착 상태가 발생하고 프로그램이 차단됩니다.

티켓 구매 속도를 늦추지 않기 위해 사용자 당 하나의 뮤텍스를 사용하여 맵에 저장했습니다. 사용자가 뮤텍스에 매핑되지 않은 경우 사용자 아이디를 키로 사용하여 맵에 새 인스턴스가 생성 된 다음 다음과 같이 뮤텍스를 사용했습니다 :

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

당신은 시도-캐치-마지막으로 블록 것을 볼 수 있습니다,나는 잠금이 항상 해제 될 수 있도록. 이제 실행은 아래 그림과 같이 표시됩니다:

잠금을 사용한 코드 실행

이 방법으로 사용자는 한 번만 참여할 수 있습니다.

물론 이런 종류의 문제를 해결하는 유일한 방법은 아닙니다. 이 경우 거래도 정말 도움이됩니다.

또한 단일 서버 인스턴스에서 문제를 해결했음을 명심하십시오. 서버가 여러 개인 경우 분산 뮤텍스를 사용하여 모든 프로세스에 잠금이 획득되었음을 알려야 합니다.

답글 남기기

이메일 주소는 공개되지 않습니다.