在闪购、抢购等并发场景下,可能会出现超卖。PHP语言没有原生的并发解决方案,需要通过其他方式来实现并发控制。列出的常见解决方案是:使用队列,设置一个额外的进程来处理队列,将并发的请求放入队列中,由额外的进程串行处理,所以不存在并发问题,但是额外进程的支持和严重处理延迟是必需的。本文不先讨论此方法。利用数据库的事务特性进行原子更新。该方法需要依赖数据库的事务特性。借助文件排他锁,在处理订单请求时,使用flock锁定一个文件,只有成功获得锁的人才可以处理订单。1、使用Redis事务特性Redis事务是原子操作,可以保证订单处理过程中数据不被其他并发进程修改。示例代码:set(array('reactor_num'=>2,//reactorthreadnum'worker_num'=>4//workerprocessnum));$http->on('request',function(swoole\_http\_request$request,swoole_http_response$response){$uniqid=uniqid('uid-',TRUE);//模拟唯一用户ID$redis=newRedis();$redis->connect('127.0.0.1',6379);//连接redis$redis->watch('rest_count');//监控rest_count是否被其他进程使用Change$rest\_count=intval($redis->get("rest_count"));//模拟唯一订单IDif($rest_count>0){$value="{$rest_count}-{$uniqid}";//表示当前订单,其中被当前用户抓取//dosomething...主要是模拟用户在抓取订单后可能要进行的一些密集计算$rand=rand(100,1000000);$sum=0;for($i=0;$i<$rand;$i++){$sum+=$i;}//Redis事务$redis->multi();$redis->lPush('uniqids',$value);$redis->decr('rest_count');$replies=$redis->exec();//执行上面的redis事务//如果rest_count的值被其他并发进程改变了,上面的事务将被回滚if(!$replies){echo"order{$value}rolledback".PHP_EOL;$redis->unwatch();});$http->开始();useabtest1$ab-t20-c10http://192.168.1.104:9509/2.使用文件独占锁(阻塞模式)在阻塞模式下,如果进程独占获取文件时其他进程正在占用锁锁,进程会挂起等待其他进程释放锁,自己获取到锁后,再进行示例代码:set(array('reactor_num'=>2,//反应器线程数'worker_num'=>4//工作进程数));$http->on('request',function(swoole\_http\_request$request,swoole_http_response$response){$uniqid=uniqid('uid-',TRUE);$redis=newRedis();$redis->connect('127.0.0.1',6379);$fp=fopen("lock.txt","w+");//阻塞(等待)模式,获取独占锁(写程序)if(flock($fp,LOCK_EX)){//锁定当前指针//成功获取锁后,可以放心处理订单$rest\_count=intval($redis->get("rest_count"));$value="{$rest_count}-{$uniqid}";if($rest_count>0){//做点什么...$rand=rand(100,1000000);$sum=0;for($i=0;$i<$rand;$i++){$sum+=$i;}$redis->lPush('uniqids',$value);$redis->decr('rest_count');}//订单处理完成后释放锁flock($fp,LOCK_UN);}fclose($fp);});$http->开始();useabtest1$ab-t20-c10http://192.168.1.104:9510/3、使用文件独占锁(非阻塞模式)非阻塞在block模式下,如果进程在获取文件的独占锁时,其他进程正在占用锁,则进程会立即判断获取锁失败,继续执行\示例代码:set(array('reactor_num'=>2,//反应器线程数'worker_num'=>4//工作进程数));$http->on('request',function(swoole\_http\_request$request,swoole_http_response$response){$uniqid=uniqid('uid-',TRUE);$redis=newRedis();$redis->connect('127.0.0.1',6379);$fp=fopen("lock.txt","w+");//非阻塞模式,如果不想flock()在加锁的时候阻塞,那么添加LOCK_NB加锁if(flock($fp,LOCK_EX|LOCK_NB))//锁定当前指针{//成功获取锁后,随意处理订单$rest\_count=intval($redis->get("rest_count"));$value="{$rest_count}-{$uniqid}";if($rest_count>0){//做某事...$rand=rand(100,1000000);$sum=0;对于($i=0;$i<$rand;$i++){$sum+=$i;}$redis->lPush('uniqids',$value);$redis->decr('rest_count');}//订单处理完成后,释放锁flock($fp,LOCK_UN);}else{//如果获取锁失败,立即进入这里执行echo"{$uniqid}-系统忙,请稍后重试".PHP_EOL;}fclose($fp);});$http->开始();使用abtest1$ab-t20-c10http://192.168.1.104:9511/最后给出三种处理方式的测试结果,对比redis事务方式:......并发级别:10测试耗时:20.002秒完成请求:8616失败请求:0传输总量:1266846字节HTML传输:0字节每秒请求数:430.77[#/sec](平均值)每个请求时间:23.214[毫秒](平均值)每个请求时间:2.321[ms](所有并发请求的平均值)传输率:61.85[
