>  기사  >  PHP의 높은 동시성 솔루션에 대한 몇 가지 생각

PHP의 높은 동시성 솔루션에 대한 몇 가지 생각

无忌哥哥
无忌哥哥원래의
2018-06-27 14:37:592148검색

급매, 깜짝 세일, 복권 추첨, 티켓 예매 등의 경우 과매도 방지를 위해 재고 수량을 제한하고 있습니다. 단, 동시에 주문하는 인원이 재고 수량을 초과하는 경우에는, 과매도 문제로 이어질 것입니다. 그러면 이 문제를 어떻게 해결해야 할까요? 제 생각은 다음과 같습니다(의사 코드): sql1: 제품 재고 조회 if (재고 수량 > 0) { //주문 생성...
sql2: 동시 재고-1}

거기에 동시성이 없으면 위의 프로세스는 정상적으로 보입니다. 두 사람이 동시에 주문하고 sql1 단계에서 두 사람이 쿼리한 인벤토리가 0보다 크므로 결국 실행됩니다. sql2를 설치하면 결국 재고가 -1이 되어 과매도 상태가 됩니다. 이는 우리가 원하는 결과가 아닙니다.

이 문제를 해결하기 위해 더 널리 알려진 아이디어를 요약했습니다.

1. 추가 단일 프로세스를 사용하여 대기열을 처리하고 주문 요청을 대기열에 넣은 다음 하나씩 처리하므로 동시성 문제가 발생하지 않습니다. , 그러나 필요합니다. 추가 백그라운드 프로세스 및 지연 문제는 당분간 여기에서 고려되지 않습니다. 여기서는 메시지 대기열을 사용할 수 있으며 종종 Memcacheq 및 Radis를 사용합니다. 예를 들어, 사용자가 확보할 수 있는 티켓이 100개인 경우 이 100개의 티켓을 캐시에 저장하고 읽고 쓸 때 잠그지 않을 수 있습니다. 동시성이 큰 경우 약 500명 정도가 티켓을 성공적으로 획득할 수 있기 때문에 500명 이후의 요청은 이벤트 종료 시 정적 페이지로 직접 전송될 수 있습니다. 500명 중 400명이 물품을 구하는 것은 불가능하다. 따라서 대기열에 입장한 순서에 따라 선착순 100명만 구매에 성공하실 수 있습니다. 다음 400명은 이벤트 종료 페이지로 바로 이동됩니다. 물론 500명을 입력하는 것은 예시일 뿐이며, 숫자를 직접 조정할 수도 있습니다. 활동 종료 페이지는 데이터베이스가 아닌 정적 페이지를 사용해야 합니다. 이렇게 하면 데이터베이스에 대한 부담이 줄어듭니다.

2. MySQL 낙관적 잠금은 예를 들어 총 재고가 2이고, 급매 이벤트가 제출되면 재고가 즉시 +1이 되고 이때 재고가 3이 되며 주문이 생성된 후라는 의미입니다. , 재고를 업데이트하기 전에 재고를 다시 쿼리합니다(주문은 물론 재고 -1이 생성되지만 걱정하지 마십시오. 재고를 다시 확인하면 반환 결과는 3입니다). 예상과 일치하는지 확인합니다. 재고 수량(여기서 예상되는 재고는 3입니다.) 일치하지 않는 경우 롤백되어 사용자에게 재고가 부족하다는 메시지가 표시됩니다. 여기서 우리는 비관적 잠금에 대해 이야기합니다. 그러면 낙관적 잠금이 있어야 합니다. 그렇죠?? 여기서는 제가 알고 있는 비관적 잠금과 낙관적 잠금에 대해 간략하게 설명하겠습니다.

비관적 잠금과 낙관적 잠금은 두 가지 일반적인 리소스 동시성 잠금입니다. 디자인 아이디어는 동시 프로그래밍에서도 매우 기본적인 개념입니다. 이 기사에서는 데이터베이스 데이터에 대한 이러한 두 가지 일반적인 잠금 메커니즘의 구현을 비교하고 체계적으로 소개합니다.

비관적 잠금

비관적 잠금의 특징은 잠금을 먼저 획득한 다음 비즈니스 작업을 수행하는 것입니다. 즉, "비관적"은 잠금 획득이 실패할 가능성이 매우 높다고 믿기 때문에 먼저 잠금을 획득했는지 확인해야 합니다. 사업 운영을 수행하기 전에 성공적으로. 일반적으로 "1개의 잠금, 2개의 확인, 3개의 업데이트"라고 불리는 것은 비관적 잠금의 사용을 의미합니다. 일반적으로 데이터베이스에 대한 비관적 잠금은 데이터베이스 자체의 지원이 필요합니다. 즉, 비관적 잠금은 일반적으로 사용되는 select...for 업데이트 작업을 통해 구현됩니다. 데이터베이스가 업데이트를 위한 선택을 실행하면 선택 항목에 있는 데이터 행의 행 잠금을 획득합니다. 따라서 동시에 실행된 업데이트를 위한 다른 선택이 동일한 행을 선택하려고 시도하면 제외가 발생합니다(행 잠금이 완료될 때까지 기다려야 함). 해제됨) 잠금 효과가 달성됩니다. 업데이트용 선택으로 획득한 행 잠금은 현재 트랜잭션이 끝나면 자동으로 해제되므로 트랜잭션 내에서 사용해야 합니다.

여기서 주목해야 할 점은 데이터베이스마다 업데이트 선택에 대한 구현 및 지원이 다르다는 것입니다. 예를 들어 Oracle은 대기 없이 업데이트 선택을 지원합니다. 즉, 잠금을 얻을 수 없는 경우 오류가 대신 즉시 보고됩니다. MySQL은 대기 옵션이 없습니다. MySQL의 또 다른 문제점은 select for update 문을 실행하는 동안 스캔된 모든 행이 잠기므로 쉽게 문제가 발생할 수 있다는 것입니다. 따라서 mysql에서 비관적 잠금을 사용하는 경우 전체 테이블 스캔 대신 인덱스를 사용해야 합니다.

낙관적 자물쇠

낙관적 자물쇠의 특징: 업무 운영을 먼저 수행하고 꼭 필요한 경우가 아니면 자물쇠를 가져가지 마세요. 즉, "낙관적"은 대부분의 경우 잠금이 성공할 것이라고 믿으므로 비즈니스 작업을 완료한 후 실제로 데이터를 업데이트하는 마지막 단계 후에 잠금을 수행하면 됩니다.

데이터베이스에 대한 낙관적 잠금 구현은 완전히 논리적이며 데이터베이스의 특별한 지원이 필요하지 않습니다. 일반적인 접근 방식은 잠가야 하는 데이터에 버전 번호나 타임스탬프를 추가한 후 다음과 같이 구현하는 것입니다.

1. SELECT data AS old_data, version AS old_version FROM … 획득된 데이터, new_data 및 new_version3 가져오기 UPDATE SET data = new_data, version = new_version WHERE version = old_versionif (업데이트된 행 > 0) { // 낙관적 잠금 획득 성공, 작업 완료} else { // 낙관적 잠금 획득 실패, 롤백 그리고 다시 시도하세요}

트랜잭션에 낙관적 잠금이 있는지 여부는 실제로 중요하지 않습니다. 기본 메커니즘은 다음과 같습니다. 데이터베이스 내부에서 동일한 행을 업데이트할 때 동시성은 허용되지 않습니다. 즉, 데이터베이스는 업데이트된 행의 쓰기 잠금을 매번 획득합니다. 이 행이 성공적으로 업데이트될 때까지 해제되지 않습니다. 따라서 업무 수행 전 잠금이 필요한 데이터의 현재 버전 번호를 획득한 후, 다시 버전 번호를 비교하여 데이터가 실제로 업데이트될 때 이전에 획득한 버전 번호와 동일한지 확인하고, 동시 수정이 발생하지 않았는지 확인하려면 버전 번호를 업데이트하세요. 업데이트가 실패할 경우, 이전 버전의 데이터가 동시에 수정되어 더 이상 존재하지 않는 것으로 간주할 수 있으며, 이때 잠금 획득에 실패했다고 간주하여 전체 비즈니스 운영을 롤백해야 합니다. 필요에 따라 전체 프로세스를 다시 시도할 수 있습니다.

이 두 가지 잠금을 요약하면 다음과 같습니다.

1. 잠금 획득 실패가 없을 때 낙관적 잠금은 비관적 잠금보다 오버헤드가 적지만 일단 실패가 발생하면 롤백 오버헤드가 상대적으로 크기 때문에 사용하기에 적합합니다. 잠금 실패 확률이 상대적으로 낮은 획득 잠금 시나리오에서는 시스템 동시성 성능이 향상될 수 있습니다.

2. 낙관적 잠금은 비즈니스 운영 중에 데이터베이스 연결을 유지할 수 없는 경우나 비관적 잠금을 적용할 수 없는 기타 장소와 같은 일부 특수한 시나리오에도 적합합니다.

3. 업데이트 결과를 토대로 판단하면 sql2에 update table Inventory=xxx로 판단 조건을 추가할 수 있습니다. 여기서 false가 반환되면 인벤토리가 부족하여 트랜잭션이 롤백된다는 의미입니다.

4. 파일 독점 잠금을 사용하면 주문 요청을 처리할 때 무리를 사용하여 파일을 잠글 수 있습니다. 이는 현재 다른 주문이 처리되고 있음을 의미합니다. "서버 사용 중" 메시지를 표시합니다. 대략적인 코드는 다음과 같습니다.

차단(대기) 모드

$fp = fopen("lock.txt", "w+");if(flock($fp,LOCK_EX)) { // 锁定当前指针,,,
    //..处理订单
    flock($fp,LOCK_UN);
}fclose($fp);

Non-blocking 모드

$fp = fopen("lock.txt", "w+");if(flock($fp,LOCK_EX | LOCK_NB)) {    //..处理订单
    flock($fp,LOCK_UN);
} else {  echo "系统繁忙,请稍后再试";
}fclose($fp);

5 분산 클러스터 서버인 경우 하나 이상의 대기열 서버가 필요합니다. 샤오미와 타오바오의 급한 쇼핑은 약간 다릅니다. 샤오미는 무겁다 잡는 순간, 할당량을 잡으면 본인 것이 되고, 주문하고 결제하면 된다. 반면에 Taobao는 결제 중 필터링에 중점을 두고 있으며, 예를 들어 10개의 항목을 판매하려는 경우 10명 이상의 사용자가 결제 중에 동시에 필터링을 수행하도록 합니다. , 동시성 양을 계층별로 항목 수를 줄입니다.

6. redis lock product_lock_key를 티켓 잠금 키로 사용하세요. redis에 product_key가 있으면 모든 사용자가 주문 프로세스에 들어갈 수 있습니다. 결제 진입시 먼저 redis에 sadd(product_lock_key, “1″)을 저장해 주시고, 반품이 성공하시면 결제 진입해주세요. 실패하면 누군가 이미 결제 프로세스에 진입했다는 의미이며, 스레드는 N초 동안 대기한 후 재귀적으로 sadd 작업을 수행합니다.


성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.