PHP并发场景的三种解决方案代码实例

admin3年前PHP教程98

在秒杀,抢购等并发场景下,可能会出现超卖的现象,在 PHP 语言中并没有原生提供并发的解决方案,因此就需要借助其他方式来实现并发控制,其实方案有很多种,今天只是举个栗子抛砖引玉,有其他更好的方案你可以自己去玩一玩就好了。

列出常见的3个解决方案有:

使用队列,额外起一个进程处理队列,并发请求都放到队列中,由额外进程串行处理,并发问题就不存在了,但是要额外进程支持以及处理延迟严重,本文不先不讨论这种方法。

利用数据库事务特征,做原子更新,此方法需要依赖数据库的事务特性。

借助文件排他锁,在处理下单请求的时候,用 flock 锁定一个文件,成功拿到锁的才能处理订单。

一、利用 Redis 事务特征

redis 事务是原子操作,可以保证订单处理的过程中数据没有被其它并发的进程修改。

示例代码:


<?php
$http = new swoole_http_server("0.0.0.0", 9509); // 监听 9509
$http->set(array(
 'reactor_num' => 2, //reactor thread num
 'worker_num' => 4 //worker process num
));
$http->on('request', function (swoole_http_request $request, swoole_http_response $response) {
 $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 http://192.168.1.104:9509/
二、利用文件排他锁 (阻塞模式)

阻塞模式下,如果进程在获取文件排他锁时,其它进程正在占用锁的话,此进程会挂起等待其它进程释放锁后,并自己获取到锁后,再往下执行。

示例代码:


<?php
$http = new swoole_http_server("0.0.0.0", 9510);
$http->set(array(
 'reactor_num' => 2, //reactor thread num
 'worker_num' => 4 //worker process num
));
$http->on('request', function (swoole_http_request $request, swoole_http_response $response) {
 $uniqid = uniqid('uid-', TRUE);
 $redis = new Redis();
 $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) {
 // 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);
 }
 fclose($fp);
});
$http->start();

使用 ab 测试


$ ab -t 20 -c 10 http://192.168.1.104:9510/
三、利用文件排他锁 (非阻塞模式)

非阻塞模式下,如果进程在获取文件排他锁时,其它进程正在占用锁的话,此进程会马上判断获取锁失败,并且继续往下执行。\

示例代码:


<?php
$http = new swoole_http_server("0.0.0.0", 9511);
$http->set(array(
 'reactor_num' => 2, //reactor thread num
 'worker_num' => 4 //worker process num
));
$http->on('request', function (swoole_http_request $request, swoole_http_response $response) {
 $uniqid = uniqid('uid-', TRUE);
 $redis = new Redis();
 $redis->connect('127.0.0.1', 6379);
 $fp = fopen("lock.txt", "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);
 } else {
 // 如果获取锁失败,马上进入这里执行
 echo "{$uniqid} - 系统繁忙,请稍后再试".PHP_EOL;
 }
 fclose($fp);
});
$http->start();

使用 ab 测试


$ ab -t 20 -c 10 http://192.168.1.104:9511/

最后给出三种处理方式的测试结果比较

redis 事务方式:


Concurrency Level:  10
Time taken for tests: 20.005 seconds
Complete requests:  17537
Failed requests:  0
Total transferred:  2578380 bytes
HTML transferred:  0 bytes
Requests per second: 876.62 [#/sec] (mean)
Time per request:  11.407 [ms] (mean)
Time per request:  1.141 [ms] (mean, across all concurrent requests)
Transfer rate:   125.86 [Kbytes/sec] received

文件排他锁(阻塞模式):


Concurrency Level:  10
Time taken for tests: 20.003 seconds
Complete requests:  8205
Failed requests:  0
Total transferred:  1206282 bytes
HTML transferred:  0 bytes
Requests per second: 410.19 [#/sec] (mean)
Time per request:  24.379 [ms] (mean)
Time per request:  2.438 [ms] (mean, across all concurrent requests)
Transfer rate:   58.89 [Kbytes/sec] received

文件排他锁(非阻塞模式):


Concurrency Level:  10
Time taken for tests: 20.002 seconds
Complete requests:  8616
Failed requests:  0
Total transferred:  1266846 bytes
HTML transferred:  0 bytes
Requests per second: 430.77 [#/sec] (mean)
Time per request:  23.214 [ms] (mean)
Time per request:  2.321 [ms] (mean, across all concurrent requests)
Transfer rate:   61.85 [Kbytes/sec] received

经测试结果对比,redis 事务方式优于文件排他锁方式,而文件排他锁方式中,非阻塞模式优于阻塞模式。

到此这篇关于PHP并发场景的三种解决方案代码实例的文章就介绍到这了,更多相关PHP并发场景的三种解决方案内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!

免责声明:本文内容来自用户上传并发布,站点仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。请核实广告和内容真实性,谨慎使用。

相关文章

美国高防服务器租用多少钱跟那些因素有关

美国高防服务器租用的价格会受多种因素影响,以下是一些可能的因素:1.服务器配置:服务器配置越高,租用价格就越高。例如,处理器、内存、磁盘空间、网络接口等都会影响价格。2.带宽:带宽是服务器可以使用的网...

PHP 实现数组分页

目录array_slicearray_chunkLimitIterator参数错误时的表现总结今天,我们就来学习一下可以实现这个能力的一些函数技巧。首先,我们还是准备好测试数据。$data = [&n...

企业租用江苏BGP高防服务器的好处

企业租用江苏BGP高防服务器的好处?企业租用江苏BGP高防服务器有以下好处:提高服务器稳定性:江苏BGP高防服务器能够有效地防御DDoS等攻击,保证企业服务器的稳定运行,避免因网络攻击而导致服务器瘫痪...

PHP 修改SESSION的生存时间案例详解

需求背景:用户希望他登录之后,浏览器就帮他记住登录状态,这样他就不用每次进入的时候,都需要登录一次。session过期时间:如果我们没有设置session的生成时间,在配置文件中默认的生存时间是144...

实例分析php常量和变量的不同

说明1、常量前没有美元符号;2、常量只能用define()函数和const关键字定义,不能用赋值语句定义;3、常量可以在任何地方定义和访问变量范围的规则;4、一旦定义了常量,就不能重新定义或取消定义,...

哪些业务网站适合选择日本服务器进行搭建

哪些业务网站适合选择日本服务器进行搭建?日本服务器可以适用于许多不同的业务网站,下面是一些适合选择日本服务器进行搭建的业务网站类型:游戏类网站:日本是一个游戏文化非常发达的国家,因此如果您是一名游戏开...