当前位置: 首页 > 后端技术 > PHP

php并发锁问题的分析和设计可以参考

时间:2023-03-30 02:36:27 PHP

在工作项目中,你会遇到一些php并发访问修改一个数据的问题。如果数据没有被锁定,就会导致数据错误。接下来分析一个金融支付锁问题。我希望能有所帮助。1无应用锁机制1.1金融支付简化版代码$total){returnfalse;}//余额$left=$total-$money;//更新余额returnsetUserLeftMoney($userId,$left);}//获取用户余额函数getUserLeftMoney($userId){if(false==is_int($userId))){return0;}$sql="选择帐户形式user_accountwhereuserid=${userId}";//$mysql=newmysql();//mysql数据库返回$mysql->query($sql);}//更新用户余额函数setUserLeftMoney($userId,$money){if(false==is_int($userId)||false==is_int($money)){returnfalse;}$sql="updateuser_accountsetaccount=${money}whereuserid=${userId}";//$mysql=newmysql();//mysql数据库返回$mysql->execute($sql);}?>1.2问题分析如果有两个运营商(p和m),都使用用户号100账户,分别在pc和移动端同时登录,100账户的总余额为1000,p算子花费200,m算子花费300个并发进程如下。p运算符:提取用户余额1000。支付后剩余800=1000-200。更新后的账户余额为800。m运算符:提取用户余额1000。支付后剩余700=1000-300。支付后,账户余额是700,两次支付后,账户余额还是700,应该是500,账户余额是500。造成这种现象的根本原因是并发操作时,p和m操作同时获取的余额数据时间都是1000。2加锁设计加锁的操作一般只有两步,一是获取锁(getLock);另一种是释放锁(releaseLock)。但是现实中加锁的方式有很多,可以通过文件来实现;数据库;内存缓存;根据这种场景,我们考虑使用策略模式。2.1类图设计如下2.2php源代码设计如下LockSystem.phpcreateLock($type,$options);}}publicfunctioncreateLock($type,$options=array()){if(false==in_array($type,self::$_supportLocks)){thrownewException("不支持${type}"锁);}$this->_lock=new$type($options);}publicfunctiongetLock($key,$timeout=ILock::EXPIRE){if(false==$this->_lockinstanceofILock){thrownewException('false==$this->_lockinstanceofILock');}$this->_lock->getLock($key,$timeout);}publicfunctionreleaseLock($key){if(false==$this->_lockinstanceofILock){thrownewException('false==$this->_lockinstanceofILock');}$this->_lock->releaseLock($key);}}interfaceILock{constEXPIRE=5;公共函数getLock($key,$timeout=self::EXPIRE);publicfunctionreleaseLock($key);}类FileLock实现ILock{private$_fp;私人$_single;publicfunction__construct($options){if(isset($options['path'])&&is_dir($options['path'])){$this->_lockPath=$options['path'].'/';}else{$this->_lockPath='/tmp/';$this->_single=isset($options['single'])?$options['single']:false;}publicfunctiongetLock($key,$timeout=self::EXPIRE){$startTime=Timer::getTimeStamp();$file=md5(__FILE__.$key);$this->fp=fopen($this->_lockPath.$file.'.lock',"w+");如果(真||$this->_single){$op=LOCK_EX+LOCK_NB;}else{$op=LOCK_EX;}if(false==flock($this->fp,$op,$a)){thrownewException('failed');}返回真;}publicfunctionreleaseLock($key){flock($this->fp,LOCK_UN);fclose($this->fp);}}类SQLLock实现ILock{publicfunction__construct($options){$this->_db=newmysql();}publicfunctiongetLock($key,$timeout=self::EXPIRE){$sql="SELECTGET_LOCK('".$key."','".$timeout."')";$res=$this->_db->query($sql);返回$res;}publicfunctionreleaseLock($key){$sql="SELECTRELEASE_LOCK('".$key."')";返回$this->_db->query($sql);}}类MemcacheLock实现ILock{publicfunction__construct($options){$this->memcache=newMemcache();}publicfunctiongetLock($key,$timeout=self::EXPIRE){$waitime=20000;$totalWaitime=0;$time=$timeout*1000000;while($totalWaitime<$time&&false==$this->memcache->add($key,1,$timeout)){usleep($waitime);$totalWaitime+=$waitime;}if($totalWaitime>=$time)thrownewException('无法获得锁等待'.$timeout.'s.');}publicfunctionreleaseLock($key){$this->memcache->delete($key);}}3应用锁机制3.1支付系统应用锁getLock($lockKey,8);//获取总金额$total=getUserLeftMoney($userId);//支出大于剩余if($money>$total){$ret=false;}else{//余额$left=$total-$money;//更新余额$ret=setUserLeftMoney($userId,$left);}//释放锁$lockSystem->releaseLock($lockKey);}猫ch(Exception$e){//释放锁$lockSystem->releaseLock($lockKey);}}//获取用户余额函数getUserLeftMoney($userId){if(false==is_int($userId)){return0;}$sql="选择帐户形式user_accountwhereuserid=${userId}";//$mysql=newmysql();//mysql数据库返回$mysql->query($sql);}//更新用户余额函数setUserLeftMoney($userId,$money){if(false==is_int($userId)||false==is_int($money)){returnfalse;}$sql="updateuser_accountsetaccount=${money}whereuserid=${userId}";//$mysql=newmysql();//mysql数据库return$mysql->execute($sql);}?>3.2锁分析p算子:获取锁:pay100取出用户1000余额支付后,剩余800=1000-200更新后的账户余额为800释放锁:pay100m操作者:等待锁:pay100获取锁:pay100获取余额:800支付后剩余500=800-300支付后,账户余额是500。释放锁:用pay100支付两次后,余额为500。完美解决了并发带来的临界区资源访问问题。