1. 동시성 문제
동시성이 무엇인지는 누구나 알고 있습니다. 여기서 말하는 것은 동일한 리소스를 점유하기 위한 여러 동시 요청입니다.
요청: index.php?mod =a&action =b&taskid=6 처리:
$key = "a_b::".$uid.'_'.$taskid; $v = $redis->get($key); if($v == 1){ $redis->setex($key,10,1); //处理逻辑省略 }
2. 분석
제가 살펴본 데이터베이스에는 두 개의 동일한 요청 결과가 작성된 것으로 나타났습니다. 맙소사! 두 요청 간의 시간 차이를 기록하기 위해 microtime(true)을 사용했는데 실제로는 $redis입니다. ->setex($key,10,1); 두 번째 요청은 첫 번째 요청과 동일한 결과를 얻었습니다. 이것이 전설적인 동시 자원 선점 아닌가요? 저는 이런 상황에 대해 여러 번 들었고, 개발 과정에서 의도적으로 시뮬레이션하지 않았습니다.
3. 솔루션
솔루션 1: 첫 번째 반응은 처리 프로세스에 트랜잭션을 추가하는 것입니다(데이터베이스는 mysql innoDB입니다). 트랜잭션을 추가한 결과 첫 번째 요청이 성공합니다. 두 번째 요청은 성공합니다. 나중에까지 실행되며, 무거운 것으로 확인되면 롤백됩니다. 실제로 mysql 트랜잭션은 데이터 일관성을 보장하는 데는 매우 뛰어나지만 롤백을 통해 고유한 리소스의 독점적 사용을 보장하기에는 비용이 너무 많이 듭니다. 나중에 삭제되었습니다.
옵션 2: 또 다른 옵션은 PHP의 파일 독점 잠금입니다. 즉, 이 경우 독점성을 달성하기 위한 사용자 수 * 작업 수로 새 파일을 생성해야 합니다. 요청된 각 리소스가 더 큰 경우 더 적은 경우 선택적 솔루션:
/** * 加锁 */ public function file_lock($filename){ $fp_key = sha1($filename); $this->fps[$fp_key] = fopen($filename, 'w+'); if($this->fps[$fp_key]){ return flock($this->fps[$fp_key], LOCK_EX|LOCK_NB); } return false; } /** * 解锁 */ public function file_unlock($filename){ $fp_key = sha1($filename); if($this->fps[$fp_key] ){ flock($this->fps[$fp_key] , LOCK_UN); fclose($this->fps[$fp_key] ); } }
옵션 3: $redis->setnx()가 원자 작업 상태를 제공할 수 있음을 확인했습니다. 동일한 키 setnx 실행 후 만료되거나 삭제되지 않았으므로 false를 다시 실행합니다. 이를 통해 두 개 이상의 동시 요청을 제어할 수 있으며 계속하려면 먼저 잠금을 성공적으로 획득해야 합니다.
/** * 加锁 */ public function task_lock($taskid){ $expire = 2; $lock_key ='task_get_reward_'.$this->uid.'_'.$taskid; $lock = $this->redis->setNX($lock_key , time());//设当前时间 if($lock){ $this->redis->expire($lock_key, $expire); //如果没执行完 2s锁失效 } if(!$lock){//如果获取锁失败 检查时间 $time = $this->redis->get($lock_key); if(time() - $time >= $expire){//添加时间戳判断为了避免expire执行失败导致死锁 当然可以用redis自带的事务来保证 $this->redis->rm($lock_key); } $lock = $this->redis->setNX($lock_key , time()); if($lock){ $this->redis->expire($lock_key, $expire); //如果没执行完 2s锁失效 } } return $lock; } /** * 解锁 */ public function task_unlock($taskid){ $this->set_redis(); $lock_key = 'task_get_reward_'.$this->uid.'_'.$taskid; $this->redis->rm($lock_key); }
setNX 및 만료 두 작업이 실제로 Redis 트랜잭션을 사용하여 일관성을 보장할 수 있음을 설명합니다