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

PHP并发场景的几种解决方案

时间:2023-03-30 00:20:40 PHP

在闪购、抢购等并发场景下,可能会出现超卖的情况。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是否被其他进程改变$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}rollback".PHP_EOL;}}$redis->unwatch();});$http->开始();使用ab测试$ab-t20-c10http://192.168.1.104:9509/2.在阻塞模式下使用文件独占锁(阻塞模式),如果进程获取文件独占时其他进程正在占用锁锁,进程会挂起等待其他进程释放锁,自己获取到锁后,再进行示例代码: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);$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->开始();使用ab测试$ab-t20-c10http://192.168.1.104:9510/3.使用文件独占锁(非阻塞模式)在非阻塞模式下,如果进程获取文件独占锁,如果其他进程正在占用锁,本进程会立即判断获取锁失败,继续执行示例代码: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);$redis=newRedis();$redis->connect('127.0.0.1',6379);$fp=fopen("lock.txt","w+");//非阻塞模式,如果不想flock()在加锁时阻塞,添加lockLOCK_NBif(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;for($i=0;$i<$rand;$i++){$sum+=$i;}$redis->lPush('uniqids',$value);$redis->decr('rest_count');}//释放订单处理完成后lockflock($fp,LOCK_UN);}else{//如果获取锁失败,立即进入这里并执行echo"{$uniqid}-系统繁忙,请稍后再试".PHP_EOL;}fclose($fp);});$http->start();使用ab测试$ab-t20-c10http://192.168.1.104:9511/最后给出三种处理方式的测试结果,对比redis事务方式:......ConcurrencyLevel:10Time进行测试:20.005秒完成请求:17537失败请求:0总传输:2578380字节HTML传输:0字节每秒请求数:876.62[#/sec](平均值)每个请求时间:11.407[ms](平均值)每个请求时间:[ms](平均值)每个请求的时间:[ms](平均值,跨所有并发请求)传输速率:125.86[Kbytes/sec]收到......文件独占锁(阻塞模式):......并发级别:10测试时间:20.003秒完成请求:8205失败请求:0传输总数:1206282字节HTML传输:0字节每秒请求数:410.19[#/sec](平均值)每个请求时间:24.379[毫秒](平均值)每个请求时间:2.438[ms](平均值,在所有concurrentrequests)传输速率:58.89[Kbytes/sec]收到......文件独占锁(非阻塞模式):......并发级别:10测试时间:20.002秒完成请求:8616失败请求:0总传输:1266846字节HTML传输:0字节每秒请求数:430.77[#/sec](平均值)每个请求时间:23.214[ms](平均值)每个请求时间:2.321[ms](平均值,跨所有并发请求)传输速率:61.85[Kbytes/sec]received...对比测试结果,redis事务方式优于文件独占锁方式,文件独占锁方式中非阻塞方式优于阻塞方式