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

PHP+Redis并发下单(完整代码)

时间:2023-03-30 05:11:52 PHP

ip=$配置['IP'];}if(isset($config['port'])){$this->ip=$config['port'];}}/***Redis连接信息可以使用原生或其他框架集成*/$this->_redis=newRedis();$this->_redis->connect($this->ip,$this->port);}/**lock*@paramint$intTimeout默认过期时间(避免死锁)*@returnbool*/privatefunctionlock($intTimeout=8){#新版本的set集成了大部分集成操作$strRet=$this->_redis->set($this->_lockKey,time().rand(10000,99999).rand(1000,9999).rand(100,999),'ex',$intTimeout,'nx');如果($strRet){返回真;}else{返回错误;}}/**Unlock*@throws\Exception*/privatefunctionunlock(){$strRet=$this->_redis->del($this->_lockKey);如果($strRet){返回真;}else{if($this->_redis->get($this->_lockKey)){返回false;}else{返回错误;}}}/***业务相关的key,可以是库存,商品数量等*/constORDER_KEY='order_num';/***用户相关键*/constUSER_KEY='user_num';/**Redisorder*@paramint$num单号*@paramstring$userId用户ID**session是为了方便异常处理和数据查找*@paramstring$bout商品session=>order_num:1,order_num:2*@returnbool*@throwsException*/publicfunctionorder(string$userId,string$bout='1',int$num=1){$orderKey=self::ORDER_KEY.':'.$回合;$userKey=self::USER_KEY.':'.$bout;//该方法没有原子并发处理,不能用于条件判断//$len=$this->_redis->llen();#实际n+1次触发完成,这里只做Redis自减$check=$this->_redis->lpop($orderKey);if(!$check){#当前order_num已经是0了!//Auto补货为100,$bout有一定的处理规则,不能乱传self::autoBuild(100,$bout);返回假;}//特殊处理,避免n+1次$len=$this->_redis->llen($orderKey);if($len==0){//自动补100,$bout有一定的处理规则,所以self::autoBuild(100,$bout);返回假;}//添加用户数据$result=$this->_redis->lpush($userKey,$userId);如果($结果){返回真;}else{返回错误;}}/**失败处理*#增加当前库存*#减少用户库存*@paramstring$num*@paramstring$userId*@param$bout*@returnbool*@throwsException*/publicfunction_out(string$num,string$userId,$bout){#并发参与时,总库存5次,共10次请求,成功5次,退款1次,实际库存1次#处理失败时,添加同_buildOrder一样的锁为避免更新下一次库存,只能同时执行上一次库存积累#_out和_buildOrder中的一个,否则锁会报错,避免不必要的死锁self::lock();//减少用户库存$user=$this->_redis->lpop(self::USER_KEY.':'.$bout);如果(!$用户){返回假;}//增加商品库存$all=$this->_redis->lpush(self::ORDER_KEY.':'.$bout,$userId);if(!$all){//TODO::这里需要进行容错处理,即当商品库存增加失败时,记录returnfalse;}自我::解锁();}/**AutoBuild*@paramint$num*@param$bout*@throwsException*/privatefunctionautoBuild(int$num,$bout){$a=$this->_redis->get(self::ORDER_KEY.':'.$bout);if(!$a){//库存已经结束$this->_buildOrder(self::ORDER_KEY.':'.$bout,$num);}}/**物品库存规则*@param$orderKey*@param$num*@returnstring*@throwsException*/privatefunction_buildOrder($orderKey,$num){//lockself::lock();$ckNum='0';#Redis运行后返回的是字符串类型#总数和$ckNum必须是同一类型,否则可能会判断错误if($num<0){thrownew\Exception('产品数量有误!');}$beforeNum=0;//向上一个库存判断()if($beforeNum>0){thrownew\Exception('商品没有卖完!');}//当前库存判断$length=$this->_redis->llen($orderKey);if($length>0){thrownew\Exception('产品已经存在!');}//生成当前库存while($ckNum<$num){if($ckNum==$num){break;}elseif($ckNum>$num){中断;}else{$ckNumum=$this->_redis->lpush($orderKey,1);如果($ckNum>=$num){中断;}}}//并发时循环成功redis不一定成功/*for($i=1;$i<=$num;$i++){$ckNum=$this->_redis->lpush(self::$_allCoin.self::getNum().':'.$coin,1);如果($ckNum>=$num){中断;}}*///解锁self::unlock();返回$ckNum;}}