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


Posted in PHP onFebruary 27, 2021

在秒杀,抢购等并发场景下,可能会出现超卖的现象,在 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并发场景的三种解决方案内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

PHP 相关文章推荐
PHP 万年历实现代码
Oct 18 PHP
深入php-fpm的两种进程管理模式详解
Jun 03 PHP
php curl选项列表(超详细)
Jul 01 PHP
php中通过curl检测页面是否被百度收录
Sep 27 PHP
CI框架中libraries,helpers,hooks文件夹详细说明
Jun 10 PHP
详谈PHP文件目录基础操作
Nov 11 PHP
PHP SPL标准库之SplFixedArray使用实例
May 12 PHP
使用PHP+MySql实现微信投票功能实例代码
Sep 29 PHP
PHP7扩展开发之基于函数方式使用lib库的方法详解
Jan 15 PHP
PHP一个简单的无需刷新爬虫
Jan 05 PHP
laravel框架 laravel-admin上传图片到oss的方法
Oct 13 PHP
基于PHP实现发微博动态代码实例
Dec 11 PHP
常见的5个PHP编码小陋习以及优化实例讲解
Feb 27 #PHP
laravel数据库查询结果自动转数组修改实例
Feb 27 #PHP
PHP的垃圾回收机制代码实例讲解
Feb 27 #PHP
php命令行模式代码实例详解
Feb 26 #PHP
PHP时间类完整代码实例
Feb 26 #PHP
PHP队列场景以及实现代码实例详解
Feb 26 #PHP
PHP实现长轮询消息实时推送功能代码实例讲解
Feb 26 #PHP
You might like
php通过COM类调用组件的实现代码
2012/01/11 PHP
php站内搜索关键词变亮的实现方法
2014/12/30 PHP
php中使用url传递数组的方法
2015/02/11 PHP
PHP7常量数组用法分析
2016/09/26 PHP
PHP命名空间namespace的定义方法详解
2017/03/29 PHP
yii2多图上传组件的使用教程
2018/05/10 PHP
讲两件事:1.this指针的用法小探. 2.ie的attachEvent和firefox的addEventListener在事件处理上的区别
2007/04/12 Javascript
JavaScript DOM学习第六章 表单实例
2010/02/19 Javascript
javascript实现的使用方向键控制光标在table单元格中切换
2010/11/17 Javascript
eval与window.eval的差别分析
2011/03/17 Javascript
silverlight线程与基于事件驱动javascript引擎(实现轨迹回放功能)
2011/08/09 Javascript
解决ExtJS在chrome或火狐中正常显示在ie中不显示的浏览器兼容问题
2013/01/11 Javascript
jQuery操作checkbox选择(list/table)
2013/04/07 Javascript
node.js读取文件到字符串的方法
2015/06/29 Javascript
jquery 实现复选框的全选操作实例代码
2017/01/24 Javascript
JS对象是否拥有某属性如何判断
2017/02/03 Javascript
ES6学习之变量的两种命名方法示例
2017/07/18 Javascript
jQuery Ajax向服务端传递数组参数值的实例代码
2017/09/03 jQuery
使用JS模拟锚点跳转的实例
2018/02/01 Javascript
JS实现简单的点赞与踩功能示例
2018/12/05 Javascript
vue.js实现左边导航切换右边内容
2019/10/21 Javascript
React学习之JSX与react事件实例分析
2020/01/06 Javascript
[45:25]OG vs EG 2019国际邀请赛淘汰赛 胜者组 BO3 第一场 8.22
2019/09/05 DOTA
Python学习笔记之视频人脸检测识别实例教程
2019/03/06 Python
pymysql的简单封装代码实例
2020/01/08 Python
Python计算公交发车时间的完整代码
2020/02/12 Python
TensorFlow tf.nn.softmax_cross_entropy_with_logits的用法
2020/04/19 Python
解决python调用自己文件函数/执行函数找不到包问题
2020/06/01 Python
CSS3实现简易版的刮刮乐效果
2016/09/27 HTML / CSS
Html5 audio标签样式的修改
2016/01/28 HTML / CSS
澳大利亚婴儿礼品公司:The Baby Gift Company
2018/11/04 全球购物
单位租房协议范本
2014/12/03 职场文书
工程质检员岗位职责
2015/04/08 职场文书
预备党员入党感言
2015/08/01 职场文书
QT与javascript交互数据的实现
2021/05/26 Javascript
MySQL数据库如何查看表占用空间大小
2022/06/10 MySQL