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新手上路(三)
Oct 09 PHP
FleaPHP的安全设置方法
Sep 15 PHP
基于curl数据采集之单页面采集函数get_html的使用
Apr 28 PHP
php生成二维码的几种方式整理及使用实例
Jun 03 PHP
php中time()和mktime()方法的区别
Sep 28 PHP
调用WordPress函数统计文章访问量及PHP原生计数器的实现
Mar 21 PHP
PHP连接MySQL进行增、删、改、查操作
Feb 19 PHP
yii2 url重写并隐藏index.php方法
Dec 10 PHP
PHP创建XML接口示例
Jul 04 PHP
PHP单元测试配置与使用方法详解
Dec 27 PHP
PHP date_default_timezone_set()设置时区操作实例分析
May 16 PHP
详解Laravel服务容器的优势
May 29 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实现快速排序法函数代码
2012/08/27 PHP
ThinkPHP实现将本地文件打包成zip下载
2014/06/26 PHP
php访问数组最后一个元素的函数end()用法
2015/03/18 PHP
PHP5.2下preg_replace函数的问题
2015/05/08 PHP
js异常捕获方法介绍
2013/04/10 Javascript
jquery 字符串切割函数substring的用法说明
2014/02/11 Javascript
java和javascript获取word文档的书签位置对比
2014/06/19 Javascript
如何在MVC应用程序中使用Jquery
2014/11/17 Javascript
Bootstrap基础学习
2015/06/16 Javascript
Jquery全选与反选点击执行一次的解决方案
2015/08/14 Javascript
Highcharts入门之基本属性
2016/08/02 Javascript
解决node.js安装包失败的几种方法
2016/09/02 Javascript
JavaScript对JSON数据进行排序和搜索
2017/07/24 Javascript
详解vue+css3做交互特效的方法
2017/11/20 Javascript
nodejs用gulp管理前端文件方法
2018/06/24 NodeJs
node实现分片下载的示例代码
2018/10/17 Javascript
玩转Koa之koa-router原理解析
2018/12/29 Javascript
Python实现去除代码前行号的方法
2015/03/10 Python
一个基于flask的web应用诞生 flask和mysql相连(4)
2017/04/11 Python
JSONLINT:python的json数据验证库实例解析
2017/11/28 Python
python中dict()的高级用法实现
2019/11/13 Python
Pandas时间序列:重采样及频率转换方式
2019/12/26 Python
基于Keras中Conv1D和Conv2D的区别说明
2020/06/19 Python
Python Pandas数据分析工具用法实例
2020/11/05 Python
CSS3中动画属性transform、transition和animation属性的区别
2016/09/25 HTML / CSS
HTML5等待加载动画效果
2017/07/27 HTML / CSS
Shell编程面试题
2016/05/29 面试题
医生实习工作总结的自我评价
2013/09/27 职场文书
材料化学应届生求职信
2013/10/09 职场文书
《小小竹排画中游》教学反思
2014/02/26 职场文书
党员干部廉洁承诺书
2014/05/28 职场文书
社区党员公开承诺书
2014/08/30 职场文书
化工实习心得体会
2014/09/09 职场文书
大学自主招生自荐信(2016精选篇)
2016/01/28 职场文书
财务年终工作总结大全
2019/06/20 职场文书
七年级作文之秋游
2019/10/21 职场文书