您当前的位置: 首页 > 慢生活 > 程序人生 网站首页程序人生
php高并发处理方案-文件锁-io阻塞方式
发布时间:2021-10-17 15:04:18编辑:雪饮阅读()
这次做接到一个团购获取遇到了重复参团的问题,那么实际上是crmeb系统,具体是单商户还是多商户,这里不太清楚了,暂且不论这个。
我个人理解就是人家这么出名的crmeb应该不会出现这种没有判断吧,可能人家本来是有判断,只是在代码很深的地方,不过我也有看到过想过注释。
那么不管是他本来有判断,只是没有考虑到高并发情况下导致判断时效性问题。
就比如说8人团,高并发时候可能同时有两个进程,第一个进程获取当前团有7人参团,则第一个进程可以参团,同时第二个进程也获取到了相同的,那么等第一个参团结束,是8人。
此时第二个也参团完成了则是9人。
那么问题的症结就是说这两个参团是并行的,并没有像是序列化一样的。
这里我的解决方案是文件锁的阻塞模式,文件锁阻塞模式下,则第一个进程锁定文件后,其它进程要想锁文件首先得等第一个文件解锁了文件才可以。这样就有点类似序列化的那个意思了。
那么具体实现如:
public function createOrder1(){
$file = fopen(__DIR__.'/lock.txt','w+');
//加锁
if(flock($file,LOCK_EX)){
//TODO 执行业务代码
flock($file,LOCK_UN);//解锁
}
//关闭文件
fclose($file);
}
那么这里需要注意,像是下单或者添加购物车的时候这样做的确挺好,但是系统处理下订单或者添加到购物车等的速度就变低了,我这里参团的实际逻辑就是添加到购物车。
则可以细化下锁的文件名,比如以某个团id或者某个商品id为锁,这样则实现了对于某个团或者某个商品进行锁,而不是只要下单或添加购物车不管是什么商品什么团都锁同一个文件,那么对于不同的团或不同的商品来说也都要先等同一个进程锁,岂不是浪费了处理性能。
小提示:在执行fclose()的时候文件会自动解锁,所以可以省略解锁的代码。
以上这些没有实打实的确认过(关键不太好确认),但是目前这个团购系统暂时还没有再次遇到同时参团超过8人的情况,我觉得应该是没有问题了的。
那么这里我再次实际测试下,测试比较麻烦,需要安装Thread扩展,而且从该扩展命名来发现该扩展目前最新版仅支持php7.0
那么下面这个是有参团bug的程序:
demo.php:
<?php
//解决cmd命令中中文乱码
exec('chcp 65001');
error_reporting(E_ALL ^ E_DEPRECATED);
class Conf {
public static $host = 'localhost';
public static $port = '3306';
public static $user = 'root';
public static $passwd = '123456';
public static $dbname = 'test';
}
class NoLock extends Thread {
public function run() {
//模拟真实环境,连接数据库,每次都返回一个新的数据库连接
$mysql = mysqli_connect(Conf::$host.':'.Conf::$port,Conf::$user,Conf::$passwd,Conf::$dbname);
//从数据库中取出库存
$sql = "select count(*) as num from goods_transaction";
$result = mysqli_query($mysql,$sql);
$row=mysqli_fetch_assoc($result);
$tid=self::getCurrentThreadId();
$str='\r\n本次线程:tid='.$tid.' 当前参团人数='.$row["num"];
if($row["num"]>=8){
$str.=",超过参团总人数\r\n";
echo $str;
}
else{
$insert_res=mysqli_query($mysql,"insert into goods_transaction values(".$tid.")");
if($insert_res){
$str.=",参团成功\r\n";
echo $str;
}
}
mysqli_close($mysql);
}
}
$mysql = mysqli_connect(Conf::$host.':'.Conf::$port,Conf::$user,Conf::$passwd,Conf::$dbname);
//清空之前的交易记录
mysqli_query($mysql,"delete from goods_transaction");
mysqli_close($mysql);
//用100个模拟用户去参团
$clientArr = [];
for ($i=0;$i<10;++$i) {
$clientArr[$i] = new NoLock();
$clientArr[$i]->start();
}
//最终结果(结果不准,因为线程是异步的,直接去数据库看看)
$mysql = mysqli_connect(Conf::$host.':'.Conf::$port,Conf::$user,Conf::$passwd,Conf::$dbname);
$sql = "select count(*) as num from goods_transaction";
$result = mysqli_query($mysql,$sql);
$row=mysqli_fetch_assoc($result);
$str="\r\n全部线程跑完后的参团人数:".$row["num"]."\r\n";
echo $str;
?>
只从运行结果就可以看到有问题:
C:\Users\Administrator>D:\phpstudy_pro\Extensions\php\php-7.0.14-Win32-VC14-x86\php.exe D:\phpstudy_pro\WWW\demo.php
全部线程跑完后的参团人数:0
\r\n本次线程:tid=14188 当前参团人数=0,参团成功
\r\n本次线程:tid=3960 当前参团人数=0,参团成功
\r\n本次线程:tid=9348 当前参团人数=0,参团成功
\r\n本次线程:tid=16952 当前参团人数=0,参团成功
\r\n本次线程:tid=16404 当前参团人数=0,参团成功
\r\n本次线程:tid=2040 当前参团人数=0,参团成功
\r\n本次线程:tid=12636 当前参团人数=0,参团成功
\r\n本次线程:tid=13556 当前参团人数=0,参团成功
\r\n本次线程:tid=16764 当前参团人数=0,参团成功
\r\n本次线程:tid=16988 当前参团人数=0,参团成功
C:\Users\Administrator>D:\phpstudy_pro\Extensions\php\php-7.0.14-Win32-VC14-x86\php.exe D:\phpstudy_pro\WWW\demo.php
\r\n本次线程:tid=16140 当前参团人数=0,参团成功
全部线程跑完后的参团人数:1
\r\n本次线程:tid=12120 当前参团人数=1,参团成功
\r\n本次线程:tid=5656 当前参团人数=1,参团成功
\r\n本次线程:tid=9192 当前参团人数=1,参团成功
\r\n本次线程:tid=14272 当前参团人数=1,参团成功
\r\n本次线程:tid=17108 当前参团人数=1,参团成功
\r\n本次线程:tid=11268 当前参团人数=1,参团成功
\r\n本次线程:tid=2812 当前参团人数=1,参团成功
\r\n本次线程:tid=4592 当前参团人数=1,参团成功
\r\n本次线程:tid=4048 当前参团人数=1,参团成功
C:\Users\Administrator>D:\phpstudy_pro\Extensions\php\php-7.0.14-Win32-VC14-x86\php.exe D:\phpstudy_pro\WWW\demo.php
\r\n本次线程:tid=9540 当前参团人数=0,参团成功
\r\n本次线程:tid=9656 当前参团人数=1,参团成功
\r\n本次线程:tid=3948 当前参团人数=2,参团成功
\r\n本次线程:tid=5904 当前参团人数=3,参团成功
全部线程跑完后的参团人数:4
\r\n本次线程:tid=17000 当前参团人数=4,参团成功
\r\n本次线程:tid=15332 当前参团人数=5,参团成功
\r\n本次线程:tid=17388 当前参团人数=5,参团成功
\r\n本次线程:tid=15172 当前参团人数=5,参团成功
\r\n本次线程:tid=12944 当前参团人数=5,参团成功
\r\n本次线程:tid=9844 当前参团人数=5,参团成功
C:\Users\Administrator>D:\phpstudy_pro\Extensions\php\php-7.0.14-Win32-VC14-x86\php.exe D:\phpstudy_pro\WWW\demo.php
全部线程跑完后的参团人数:0
\r\n本次线程:tid=1772 当前参团人数=0,参团成功
\r\n本次线程:tid=16768 当前参团人数=0,参团成功
\r\n本次线程:tid=16864 当前参团人数=0,参团成功
\r\n本次线程:tid=17372 当前参团人数=0,参团成功
\r\n本次线程:tid=12728 当前参团人数=0,参团成功
\r\n本次线程:tid=12508 当前参团人数=0,参团成功
\r\n本次线程:tid=17176 当前参团人数=0,参团成功
\r\n本次线程:tid=7768 当前参团人数=0,参团成功
\r\n本次线程:tid=16596 当前参团人数=0,参团成功
\r\n本次线程:tid=15940 当前参团人数=0,参团成功
那么下面这个是解决了参团限制bug的程序:
demo2.php:
<?php
//解决cmd命令中中文乱码
exec('chcp 65001');
error_reporting(E_ALL ^ E_DEPRECATED);
class Conf {
public static $host = 'localhost';
public static $port = '3306';
public static $user = 'root';
public static $passwd = '123456';
public static $dbname = 'test';
}
class NoLock extends Thread {
public function run() {
$file = fopen(__DIR__.'/lock.txt','w+');
//加锁
if(flock($file,LOCK_EX)){
//模拟真实环境,连接数据库,每次都返回一个新的数据库连接
$mysql = mysqli_connect(Conf::$host.':'.Conf::$port,Conf::$user,Conf::$passwd,Conf::$dbname);
//从数据库中取出库存
$sql = "select count(*) as num from goods_transaction";
$result = mysqli_query($mysql,$sql);
$row=mysqli_fetch_assoc($result);
$tid=self::getCurrentThreadId();
$str='\r\n本次线程:tid='.$tid.' 当前参团人数='.$row["num"];
if($row["num"]>=8){
$str.=",超过参团总人数\r\n";
echo $str;
}
else{
$insert_res=mysqli_query($mysql,"insert into goods_transaction values(".$tid.")");
if($insert_res){
$str.=",参团成功\r\n";
echo $str;
}
}
mysqli_close($mysql);
flock($file,LOCK_UN);//解锁
}
//关闭文件
fclose($file);
}
}
$mysql = mysqli_connect(Conf::$host.':'.Conf::$port,Conf::$user,Conf::$passwd,Conf::$dbname);
//清空之前的交易记录
mysqli_query($mysql,"delete from goods_transaction");
mysqli_close($mysql);
//用100个模拟用户去参团
$clientArr = [];
for ($i=0;$i<10;++$i) {
$clientArr[$i] = new NoLock();
$clientArr[$i]->start();
}
//最终结果(结果不准,因为线程是异步的,直接去数据库看看)
$mysql = mysqli_connect(Conf::$host.':'.Conf::$port,Conf::$user,Conf::$passwd,Conf::$dbname);
$sql = "select count(*) as num from goods_transaction";
$result = mysqli_query($mysql,$sql);
$row=mysqli_fetch_assoc($result);
$str="\r\n全部线程跑完后的参团人数:".$row["num"]."\r\n";
echo $str;
?>
运行结果就很妙:
C:\Users\Administrator>D:\phpstudy_pro\Extensions\php\php-7.0.14-Win32-VC14-x86\php.exe D:\phpstudy_pro\WWW\demo2.php
\r\n本次线程:tid=17296 当前参团人数=0,参团成功
全部线程跑完后的参团人数:1
\r\n本次线程:tid=1308 当前参团人数=1,参团成功
\r\n本次线程:tid=5968 当前参团人数=2,参团成功
\r\n本次线程:tid=1268 当前参团人数=3,参团成功
\r\n本次线程:tid=2216 当前参团人数=4,参团成功
\r\n本次线程:tid=10248 当前参团人数=5,参团成功
\r\n本次线程:tid=15184 当前参团人数=6,参团成功
\r\n本次线程:tid=15432 当前参团人数=7,参团成功
\r\n本次线程:tid=14144 当前参团人数=8,超过参团总人数
\r\n本次线程:tid=168 当前参团人数=8,超过参团总人数
关键字词:高并发,文件锁,io阻塞
上一篇:thinkphp6 db层面(非model形式)对于查询的字段排除(隐藏)的实现
下一篇:解决php线程Thread无法启动start时报错PHP Fatal error: Uncaught RuntimeException: cannot start