이번에는 PHP에서 동시 반복 쓰기를 방지하는 방법과 동시 반복 쓰기를 방지하기 위한 PHP의 주의 사항에 대해 설명하겠습니다. 실제 사례를 살펴보겠습니다.
1. 앞에 작성:
전체 공급망 시스템에는 작성을 위한 인터페이스와 관련하여 다양한 종류의 문서(구매 주문, 창고 주문, 도착 주문, 운송장 등)가 있습니다. 문서 데이터(추가, 삭제 및 수정 작업), 프런트 엔드에서 관련 제한을 구현하더라도 네트워크 또는 비정상적인 작업으로 인해 동시 반복 호출이 발생하여 동일한 문서가 동일하게 처리될 수 있습니다.
이러한 상황이 시스템에 비정상적인 영향을 미치는 것을 방지하기 위해 Redis를 통해 간단한 문서 잠금을 구현합니다. 각 요청은 비즈니스 로직을 실행하기 전에 잠금을 획득해야 하며 실행이 완료된 후 잠금이 해제됩니다. 동일한 문서의 동시 반복 작업 요청에 대해 단 한 번의 요청만 잠금을 획득할 수 있음이 보장됩니다(Redis에 의존). 이는 비관적 잠금 설계입니다.
참고: Redis 잠금은 일반적으로 동시 반복 요청을 해결하는 데만 사용됩니다. 비동시 반복 요청의 경우 일반적으로 데이터베이스 또는 로그를 사용하여 데이터 상태를 확인하며, 두 메커니즘을 결합하면 전체 링크의 신뢰성을 보장할 수 있습니다.
2. 잠금 메커니즘:
주로 Redis setnx 명령을 사용하여 구현합니다.
그러나 setnx 사용에 문제가 있습니다. 즉, setnx 명령은 만료 시간 설정을 지원하지 않습니다. 키에 대해 별도의 타임아웃을 설정하려면expire 명령을 사용해야 하며, 전체 잠금 작업이 원자적 작업이 아니도록 setnx 잠금은 성공했지만 비정상적인 종료로 인해 타임아웃이 성공적으로 설정되지 않았을 가능성이 있습니다. 프로그램의 잠금이 제때에 잠금 해제되지 않으면 교착 상태가 발생할 수 있습니다(비즈니스가 현장에 교착 상태가 없고 쓸모 없는 키를 메모리에 보관하는 것은 좋은 디자인이 아닙니다). 이 상황은 Redis 트랜잭션을 사용하여 해결할 수 있으며 setnx 및 만료 두 명령은 원자 연산으로 실행되지만 이 방법은 상대적으로 번거롭습니다. 다행히 Redis 2.6.12 이후 버전에서는 Redis set 명령이 nx를 지원합니다. 및 ex 모드, 그리고 만료 시간의 원자적 설정 지원:
3. 잠금 구현(완전한 테스트 코드는 마지막에 게시됩니다): /**
* 加单据锁
* @param int $intOrderId 单据ID
* @param int $intExpireTime 锁过期时间(秒)
* @return bool|int 加锁成功返回唯一锁ID,加锁失败返回false
*/
public static function addLock($intOrderId, $intExpireTime = self::REDIS_LOCK_DEFAULT_EXPIRE_TIME)
{
//参数校验
if (empty($intOrderId) || $intExpireTime <= 0) {
return false;
}
//获取Redis连接
$objRedisConn = self::getRedisConn();
//生成唯一锁ID,解锁需持有此ID
$intUniqueLockId = self::generateUniqueLockId();
//根据模板,结合单据ID,生成唯一Redis key(一般来说,单据ID在业务中系统中唯一的)
$strKey = sprintf(self::REDIS_LOCK_KEY_TEMPLATE, $intOrderId);
//加锁(通过Redis setnx指令实现,从Redis 2.6.12开始,通过set指令可选参数也可以实现setnx,同时可原子化地设置超时时间)
$bolRes = $objRedisConn->set($strKey, $intUniqueLockId, ['nx', 'ex'=>$intExpireTime]);
//加锁成功返回锁ID,加锁失败返回false
return $bolRes ? $intUniqueLockId : $bolRes;
}
잠금 해제는 의미합니다. 잠금 시 고유한 잠금 ID를 비교합니다. 비교가 성공하면 키가 삭제됩니다. 전체 잠금 해제 프로세스 중에 redis의 감시 및 트랜잭션 구현에 의존하는 원자성도 보장해야 합니다.
WATCH 명령은 하나 이상의 키를 모니터링할 수 있습니다. 키 중 하나가 수정(또는 삭제)되면 후속 트랜잭션이 실행되지 않습니다. EXEC 명령까지 모니터링이 계속됩니다. (트랜잭션의 명령은 EXEC 이후에 실행되므로 WATCH 모니터링의 키 값은 MULTI 명령 이후에 수정될 수 있습니다.)
5. 잠금 해제 구현 (완전한 테스트 코드는 마지막에 게시됩니다) ):
/** * 解单据锁 * @param int $intOrderId 单据ID * @param int $intLockId 锁唯一ID * @return bool */ public static function releaseLock($intOrderId, $intLockId) { //参数校验 if (empty($intOrderId) || empty($intLockId)) { return false; } //获取Redis连接 $objRedisConn = self::getRedisConn(); //生成Redis key $strKey = sprintf(self::REDIS_LOCK_KEY_TEMPLATE, $intOrderId); //监听Redis key防止在【比对lock id】与【解锁事务执行过程中】被修改或删除,提交事务后会自动取消监控,其他情况需手动解除监控 $objRedisConn->watch($strKey); if ($intLockId == $objRedisConn->get($strKey)) { $objRedisConn->multi()->del($strKey)->exec(); return true; } $objRedisConn->unwatch(); return false; }
connect($strIp, $intPort); return $objRedis; } /** * 用于生成唯一的锁ID的redis key */ const REDIS_LOCK_UNIQUE_ID_KEY = 'lock_unique_id'; /** * 生成锁唯一ID(通过Redis incr指令实现简易版本,可结合日期、时间戳、取余、字符串填充、随机数等函数,生成指定位数唯一ID) * @return mixed */ public static function generateUniqueLockId() { return self::getRedisConn()->incr(self::REDIS_LOCK_UNIQUE_ID_KEY); } } //test $res1 = Lock_Service::addLock('666666'); var_dump($res1);//返回lock id,加锁成功 $res2 = Lock_Service::addLock('666666'); var_dump($res2);//false,加锁失败 $res3 = Lock_Service::releaseLock('666666', $res1); var_dump($res3);//true,解锁成功 $res4 = Lock_Service::releaseLock('666666', $res1); var_dump($res4);//false,解锁失败
PHP에서 웹 클러스터 세션 동기화를 설정하는 단계에 대한 자세한 설명
PHP에서 대용량 파일 잘라내기 및 병합을 구현하는 단계에 대한 자세한 설명
위 내용은 PHP에서 동시 반복 쓰기를 방지하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!