PHP⾼并发场景的三种解决⽅案
在秒杀,抢购等并发场景下,可能会出现超卖的现象,在 PHP 语⾔中并没有原⽣提供并发的解决⽅案,因此就需要借助其他⽅式来实现并发控制,其实⽅案有很多种,今天只是举个栗⼦抛砖引⽟,有其他更好的⽅案你可以⾃⼰去玩⼀玩就好了。
列出常见的3个解决⽅案有:
使⽤队列,额外起⼀个进程处理队列,并发请求都放到队列中,由额外进程串⾏处理,并发问题就不存在了,但是要额外进程⽀持以及处理延迟严重,本⽂不先不讨论这种⽅法。
利⽤数据库事务特征,做原⼦更新,此⽅法需要依赖数据库的事务特性。
借助⽂件排他锁,在处理下单请求的时候,⽤ flock 锁定⼀个⽂件,成功拿到锁的才能处理订单。
大班折纸
汉英字典⼀、利⽤ Redis 事务特征
redis 事务是原⼦操作,可以保证订单处理的过程中数据没有被其它并发的进程修改。
梦见打电话⽰例代码:
<?php
$http=new swoole_http_rver("0.0.0.0",9509);// 监听 9509
$http->t(array(
'reactor_num'=>2,//reactor thread num
'worker_num'=>4//worker process num
));
$http->on('request',function(swoole_http_request $request, swoole_http_respon $respon){
$uniqid=uniqid('uid-',TRUE);// 模拟唯⼀⽤户ID
$redis=new Redis();
$redis->connect('127.0.0.1',6379);// 连接 redis
$redis->watch('rest_count');// 监测 rest_count 是否被其它的进程更改
$rest_count=intval($redis->get("rest_count"));// 模拟唯⼀订单ID
if($rest_count>0){
$value="{$rest_count}-{$uniqid}";// 表⽰当前订单,被当前⽤户抢到了
// do something ... 主要是模拟⽤户抢到单后可能要进⾏的⼀些密集运算
$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"订单{$value}回滚".PHP_EOL;
}
}
$redis->unwatch();
});
$http->start();
使⽤ ab 测试
$ ab -t 20-c 10 192.168.1.104:9509/
⼆、利⽤⽂件排他锁 (阻塞模式)
阻塞模式下,如果进程在获取⽂件排他锁时,其它进程正在占⽤锁的话,此进程会挂起等待其它进程释放锁后,并⾃⼰获取到锁后,再往下执⾏。
⽰例代码:
'worker_num'=>4//worker process num
));
$http->on('request',function(swoole_http_request $request, swoole_http_respon $respon){
$uniqid=uniqid('uid-',TRUE);
$redis=new Redis();
$redis->connect('127.0.0.1',6379);
$fp=fopen("","w+");
// 阻塞(等待)模式,要取得独占锁定(写⼊的程序)
if(flock($fp,LOCK_EX)){//锁定当前指针
// 成功取得锁后,放⼼处理订单
$rest_count=intval($redis->get("rest_count"));
$value="{$rest_count}-{$uniqid}";
if($rest_count>0){
// do something ...
$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);
劳动记录卡
}
fclo($fp);
});
$http->start();
使⽤ ab 测试
$ ab -t 20-c 10 192.168.1.104:9510/
三、利⽤⽂件排他锁 (⾮阻塞模式)
⾮阻塞模式下,如果进程在获取⽂件排他锁时,其它进程正在占⽤锁的话,此进程会马上判断获取锁失败,并且继续往下执⾏。\⽰例代码:
'worker_num'=>4//worker process num
));
$http->on('request',function(swoole_http_request $request, swoole_http_respon $respon){ $uniqid=uniqid('uid-',TRUE);
工作三年$redis=new Redis();
$redis->connect('127.0.0.1',6379);
$fp=fopen("","w+");
// ⾮阻塞模式,如果不希望 flock() 在锁定时堵塞,则给 lock 加上 LOCK_NB
if(flock($fp,LOCK_EX|LOCK_NB))//锁定当前指针
{
// 成功取得锁后,放⼼处理订单
$rest_count=intval($redis->get("rest_count"));
$value="{$rest_count}-{$uniqid}";
if($rest_count>0){
// do something ...
$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);
}el{
// 如果获取锁失败,马上进⼊这⾥执⾏
echo"{$uniqid} - 系统繁忙,请稍后再试".PHP_EOL;
}耶稣讲道
fclo($fp);
});
$http->start();
使⽤ ab 测试
$ ab -t 20-c 10 192.168.1.104:9511/
最后给出三种处理⽅式的测试结果⽐较
redis 事务⽅式:
Concurrency Level:10
Time taken for tests:20.005 conds
Complete requests:17537
Failed requests:0
Total transferred:2578380 bytes
HTML transferred:0 bytes
Requests per cond:876.62[#/c] (mean)
Time per request:11.407[ms](mean)
Time per request: 1.141[ms](mean, across all concurrent requests)
Transfer rate:125.86[Kbytes/c] received
⽂件排他锁(阻塞模式):
Concurrency Level:10
Time taken for tests:20.003 conds
Complete requests:8205
Failed requests:0
Total transferred:1206282 bytes
HTML transferred:0 bytes
Requests per cond:410.19[#/c] (mean)
Time per request:24.379[ms](mean)
Time per request: 2.438[ms](mean, across all concurrent requests)
Transfer rate:58.89[Kbytes/c] received
⽂件排他锁(⾮阻塞模式):
Concurrency Level:10
Time taken for tests:20.002 conds
Complete requests:8616
Failed requests:0
Total transferred:1266846 bytes
HTML transferred:0 bytes
Requests per cond:430.77[#/c] (mean)
陈永
Time per request:23.214[ms](mean)
Time per request: 2.321[ms](mean, across all concurrent requests)
Transfer rate:61.85[Kbytes/c] received
经测试结果对⽐,redis 事务⽅式优于⽂件排他锁⽅式,⽽⽂件排他锁⽅式中,⾮阻塞模式优于阻塞模式。
以上内容希望帮助到⼤家, 很多PHPer在进阶的时候总会遇到⼀些问题和瓶颈,业务代码写多了没有⽅向感,不知道该从那⾥⼊⼿去提升,对此我整理了⼀些资料,包括但不限于:分布式架构、⾼可扩展、⾼性能、⾼并发、服务器性能调优、
TP6,laravel,Redis,Swoole、Swoft、Kafka、Mysql优化、shell脚本、Docker、微服务、Nginx等多个知识点⾼级进阶⼲货需要的可以免费分享给⼤家 ,需要戳这⾥