>  기사  >  백엔드 개발  >  PHP 동시성 제어의 독점 잠금

PHP 동시성 제어의 독점 잠금

高洛峰
高洛峰원래의
2016-10-20 13:57:511306검색

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 트랜잭션을 사용하여 일관성을 보장할 수 있음을 설명합니다

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