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文本操作类
Nov 25 PHP
PHP PDO函数库详解
Apr 27 PHP
PHP新手入门学习方法
May 08 PHP
使用PHPMyAdmin修复论坛数据库的图文方法
Jan 09 PHP
php curl选项列表(超详细)
Jul 01 PHP
一组PHP加密解密函数分享
Jun 05 PHP
PHP中生成UUID自定义函数分享
Jun 10 PHP
win平台安装配置Nginx+php+mysql 环境
Jan 12 PHP
php实现带读写分离功能的MySQL类完整实例
Jul 28 PHP
PHP编写简单的App接口
Aug 28 PHP
TP5框架实现的数据库备份功能示例
Apr 05 PHP
详解PHP Swoole与TCP三次握手
May 27 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
php 获取mysql数据库信息代码
2009/03/12 PHP
测试php函数的方法
2013/11/13 PHP
简单实用的网站PHP缓存类实例
2014/07/18 PHP
适合PHP初学者阅读的4本经典书籍
2016/09/23 PHP
php使用PDO下exec()函数查询执行后受影响行数的方法
2017/03/28 PHP
stripos函数知识点实例分享
2019/02/11 PHP
thinkPHP5.1框架路由::get、post请求简单用法示例
2019/05/06 PHP
关于JavaScript的gzip静态压缩方法
2007/01/05 Javascript
extjs 列表框(multiselect)的动态添加列表项的方法
2009/07/31 Javascript
JQuery的read函数与js的onload不同方式实现
2013/03/18 Javascript
PHP结合jQuery实现红蓝投票功能特效
2015/07/22 Javascript
Javascript实现图片不间断滚动的代码
2016/06/22 Javascript
jQuery+CSS3文字跑马灯特效的简单实现
2016/06/25 Javascript
js实现动态显示时间效果
2017/03/06 Javascript
ES6中Array.find()和findIndex()函数的用法详解
2017/09/16 Javascript
微信小程序block的使用教程
2018/04/01 Javascript
Vue列表渲染的示例代码
2018/11/01 Javascript
Vue js 的生命周期(看了就懂)(推荐)
2019/03/29 Javascript
JavaScript从原型到原型链深入理解
2019/06/03 Javascript
Python实现根据指定端口探测服务器/模块部署的方法
2014/08/25 Python
Python简单进程锁代码实例
2015/04/27 Python
使用python实现rsa算法代码
2016/02/17 Python
浅析Git版本控制器使用
2017/12/10 Python
Python处理CSV与List的转换方法
2018/04/19 Python
python通过Windows下远程控制Linux系统
2018/06/20 Python
Python解析Excle文件中的数据方法
2018/10/23 Python
在django中form的label和verbose name的区别说明
2020/05/20 Python
英国领先的运动物理治疗供应公司:Vivomed
2018/07/14 全球购物
Tostadora意大利:定制T恤
2019/04/08 全球购物
事业单位请假制度
2014/01/13 职场文书
大学生职业生涯规划书范文
2014/01/14 职场文书
2014年最新学习全国两会精神心得
2014/03/17 职场文书
超市促销活动总结
2014/07/01 职场文书
项目申请汇报材料
2014/08/16 职场文书
四风问题自查自纠工作情况报告
2014/10/28 职场文书
安全生产警示教育活动总结
2015/05/09 职场文书