PHP多线程模拟实现秒杀抢单


Posted in PHP onFebruary 07, 2018

应集团要求给服务号做了个抢单秒杀的功能,需要对秒杀做个测试,想试试PHP多线程,就模拟了下抢单功能。

先说秒杀模块的思路:

正常情况下的用户秒杀操作

1、发起秒杀请求
2、进入秒杀队列
3、随机滞后 1 - 2 秒进行秒杀结果查询请求(算是变相分流吧)
4、成功则生成订单
5、返回结果

以下是模拟秒杀的代码:

<?php


set_time_limit(0);

/**
* 线程的执行任务
*/
class Threadrun extends Thread
{
  public $url;
  public $data;
  public $params;

  public function __construct($url, $params=[])
  {
   $this->url = $url;
   $this->params = $params;
  }

  public function run()
  {
   if(($url = $this->url))
   {
     $params = [
      'goods_id'  => 1,
      'activity_id'  => 1,
      'user_id'   => isset($this->params['user_id']) ? $this->params['user_id'] : $this->getCurrentThreadId(),
     ];
     $startTime = microtime(true);
     $this->data = [
      'id'   => $params['user_id'],
      'result'  => model_http_curl_get( $url, $params ),
      'time'  => microtime(true)-$startTime,
      'now'   => microtime(true),
     ];
   }
  }
}

/**
* 执行多线程
*/
function model_thread_result_get($urls_array)
{
  foreach ($urls_array as $key => $value)
  {
   $threadPool[$key] = new Threadrun($value["url"],['user_id'=>$value['user_id']]);
   $threadPool[$key]->start();
  }
  foreach ($threadPool as $thread_key => $thread_value)
  {
   while($threadPool[$thread_key]->isRunning())
   {
     usleep(10);
   }
   if($threadPool[$thread_key]->join())
   {
     $variable_data[$thread_key] = $threadPool[$thread_key]->data;
   }
  }
  return $variable_data;
}

/**
* 发送 HTTP 请求
*/
function model_http_curl_get($url,$data=[],$userAgent="")
{
  $userAgent = $userAgent ? $userAgent : 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.2)';
  $curl = curl_init();
  curl_setopt($curl, CURLOPT_URL, $url);
  curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
  curl_setopt($curl, CURLOPT_TIMEOUT, 5);
  curl_setopt($curl, CURLOPT_USERAGENT, $userAgent);
  curl_setopt($curl, CURLOPT_POST, true);
  if( !empty($data) ) {
   curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
  }
  $result = curl_exec($curl);
  curl_close($curl);
  return $result;
}


/**
 * 友好的打印变量
 * @param $val
 */
function dump( $val )
{
  echo '<pre>';
  var_dump($val);
  echo '</pre>';
}

/**
 * 写日志
 * @param $msg
 * @param string $logPath
 */
function writeLog( $msg, $logPath='' ) {
  if( empty($logPath) ) {
   $logPath = date('Y_m_d').'.log';
  }
  if( !file_exists($logPath) ) {
   $fp = fopen( $logPath,'w' );
   fclose( $fp );
  }
  error_log( $msg.PHP_EOL, 3, $logPath);
}

/**
 * 生成日志信息
 * @param $result
 * @param $timeDiff
 * @return bool|string
 */
function createLog( $result, $timeDiff ){
  if( empty($result) || !is_array($result) ) {
   return false;
  }
  $succeed = 0;
  $fail = 0;
  foreach( $result as $v ) {
   $times[] = $v['time'];
   $v['result'] === false ? $fail++ : $succeed++;
  }
  $totalTime = array_sum( $times );
  $maxTime = max( $times );
  $minTime = min( $times );
  $sum = count( $times );
  $avgTime = $totalTime/$sum;
  $segment = str_repeat('=',100);
  $flag = $segment . PHP_EOL;
  $flag .= '总共执行时间:' . $timeDiff . PHP_EOL ;
  $flag .= '最大执行时间:' . $maxTime . PHP_EOL;
  $flag .= '最小执行时间:' . $minTime . PHP_EOL;
  $flag .= '平均请求时间:' . $avgTime . PHP_EOL;
  $flag .= '请求数:' . $sum . PHP_EOL;
  $flag .= '请求成功数:' . $succeed . PHP_EOL;
  $flag .= '请求失败数:' . $fail . PHP_EOL;
  $flag .= $segment . PHP_EOL;
  return $flag;

}


/**
 * 发起秒杀请求
 */
function insertList( $urls, $logPath='' )
{
  $t = microtime(true);
  $result = model_thread_result_get($urls);
  $e = microtime(true);
  $timeDiff = $e-$t;
  echo "总执行时间:" . $timeDiff . PHP_EOL;
  foreach( $result as $v ) {
   $msg = '用户【' . $v['id'] . '】秒杀商品, 返回结果 ' . $v['result'] . ' 用时【' . $v['time'] . ' 秒】 当前时间【'.$v['now'].'】';
   writeLog( $msg,$logPath );
  }
  $logStr = createLog( $result, $timeDiff);
  writeLog( $logStr, $logPath );
  return $result;
}


//发起秒杀请求
for ($i=0; $i < 1000; $i++)
{
  $urls_array[] = array("name" => "baidu", "url" => "http://***.***.com/seckill/shopping/listinsert");
}

$list = insertList( $urls_array, './inset.log' );

//发起秒杀结果查询请求
$urls_array = [];
foreach( $list as $v ) {
  if( $v['result'] === false ) {
   continue;
  }
  $urls_array[] = array(
        "name"  => "baidu",
        "url"  => "http://***.***.com/seckill/shopping/query",
        'user_id' => $v['id'],
  );
}
insertList( $urls_array, './query.log' );

测试代码机器性能(开发机):

PHP多线程模拟实现秒杀抢单

订单代码机器性能(测试机):

PHP多线程模拟实现秒杀抢单

系统测试结果:

模拟 1000 并发的情况,单机每秒 300+ 订单,服务器毫无压力。
反倒是测试机受不了了,CPU 飙升 100%。 Apache 偶尔崩溃。

不知道是 PHP 多线程和 Windows 环境的支持不好,还是 PHP 多线程本身的问题,区区 1000 线程跑不动。多线程的地方还是比较需要 Python 和 C 出马。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

PHP 相关文章推荐
php查看session内容的函数
Aug 27 PHP
php从memcache读取数据再批量写入mysql的方法
Dec 29 PHP
PHP实现连接设备、通讯和发送命令的方法
Oct 13 PHP
简要剖析PHP的Yii框架的组件化机制的基本知识
Mar 17 PHP
利用PHP自动生成印有用户信息的名片
Aug 01 PHP
Laravel实现表单提交
May 07 PHP
php写app接口并返回json数据的实例(分享)
May 20 PHP
php 二维数组快速排序算法的实现代码
Oct 17 PHP
PHP实现求两个字符串最长公共子串的方法示例
Nov 17 PHP
mongodb和php的用法详解
Mar 25 PHP
PHP中非常有用却鲜有人知的函数集锦
Aug 17 PHP
php array_map()函数实例用法
Mar 03 PHP
PHP设计模式之装饰器模式实例详解
Feb 07 #PHP
PHP使用星号替代用户名手机和邮箱的实现代码
Feb 07 #PHP
PHP unlink与rmdir删除目录及目录下所有文件实例代码
Feb 07 #PHP
php删除一个路径下的所有文件夹和文件的方法
Feb 07 #PHP
浅析PHP类的反射来实现依赖注入过程
Feb 06 #PHP
php打开本地exe程序,js打开本地exe应用程序,并传递相关参数方法
Feb 06 #PHP
PHP给源代码加密的几种方法汇总(推荐)
Feb 06 #PHP
You might like
thinkPHP的Html模板标签使用方法
2012/11/13 PHP
PHP字符串word末字符实现大小写互换的方法
2014/11/10 PHP
php计划任务之验证是否有多个进程调用同一个job的方法
2015/12/07 PHP
php数组函数array_walk用法示例
2016/05/26 PHP
thinkphp5.0自定义验证规则使用方法
2017/11/16 PHP
php菜单/评论数据递归分级算法的实现方法
2019/08/01 PHP
javawscript 三级菜单的实现原理
2009/07/01 Javascript
jQuery 表格工具集
2010/04/25 Javascript
JavaScript几种形式的树结构菜单
2010/05/10 Javascript
jquery异步调用页面后台方法&amp;#8207;(asp.net)
2011/03/01 Javascript
三种动态加载js的jquery实例代码另附去除js方法
2014/04/30 Javascript
JavaScript中实现异步编程模式的4种方法
2014/09/24 Javascript
非常棒的jQuery图片轮播效果
2016/04/17 Javascript
Nodejs抓取html页面内容(推荐)
2016/08/11 NodeJs
BootStrap iCheck插件全选与获取value值的解决方法
2016/08/24 Javascript
Angularjs中controller的三种写法分享
2016/09/21 Javascript
jquery实现左右轮播切换效果
2018/01/01 jQuery
微信小程序 冒泡事件原理解析
2019/09/27 Javascript
vue打包通过image-webpack-loader插件对图片压缩优化操作
2020/11/12 Javascript
使用python 获取进程pid号的方法
2014/03/10 Python
如何解决django配置settings时遇到Could not import settings 'conf.local'
2014/11/18 Python
Python OS模块常用函数说明
2015/05/23 Python
python开发之tkinter实现图形随鼠标移动的方法
2015/11/11 Python
Pandas实现数据类型转换的一些小技巧汇总
2018/05/07 Python
Python常见数字运算操作实例小结
2019/03/22 Python
python dlib人脸识别代码实例
2019/04/04 Python
解决python 找不到module的问题
2020/02/12 Python
Python判断字符串是否为合法标示符操作
2020/09/03 Python
MVMT手表官方网站:时尚又实惠的高品质手表
2016/12/04 全球购物
Argos官网:英国家喻户晓的百货零售连锁商
2017/04/03 全球购物
豪华复古化妆:Besame Cosmetics
2019/09/06 全球购物
工作的心得体会
2013/12/31 职场文书
电子商务专业毕业生求职信
2014/06/12 职场文书
2014年后勤工作总结
2014/11/18 职场文书
自我工作评价范文
2015/03/06 职场文书
公司行政助理岗位职责
2015/04/11 职场文书