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 相关文章推荐
第十二节--类的自动加载
Nov 16 PHP
PHP 获取客户端真实IP地址多种方法小结
May 15 PHP
php将会员数据导入到ucenter的代码
Jul 18 PHP
使用Linux五年积累的一些经验技巧
Jun 20 PHP
PHP常用数组函数介绍
Jul 28 PHP
php使用array_search函数实现数组查找的方法
Jun 12 PHP
php获取网站百度快照日期的方法
Jul 29 PHP
php实现可逆加密的方法
Aug 11 PHP
php curl抓取网页的介绍和推广及使用CURL抓取淘宝页面集成方法
Nov 30 PHP
全面了解PHP中的全局变量
Jun 17 PHP
Laravle eloquent 多对多模型关联实例详解
Nov 22 PHP
PHP生成随机码的思路与方法实例探索
Apr 11 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 文件夹删除、php清除缓存程序
2009/08/25 PHP
PHP中使用正则表达式提取中文实现笔记
2015/01/20 PHP
学习PHP session的传递方式
2016/06/15 PHP
如何使用PHP给图片加水印
2016/10/12 PHP
PHP读取大文件的几种方法介绍
2016/10/27 PHP
ThinkPHP框架实现的邮箱激活功能示例
2018/06/15 PHP
yii2 开发api接口时优雅的处理全局异常的方法
2019/05/14 PHP
IE Firefox 使用自定义标签的区别
2009/10/15 Javascript
文本框输入时 实现自动提示(像百度、google一样)
2012/04/05 Javascript
Jquery 获取指定标签的对象及属性的设置与移除
2014/05/29 Javascript
JS实现很酷的EMAIL地址添加功能实例
2015/02/28 Javascript
jQuery实现的多屏图像图层切换效果实例
2015/05/07 Javascript
vue自定义js图片碎片轮播图切换效果的实现代码
2019/04/28 Javascript
swiper4实现移动端导航切换
2020/10/16 Javascript
vue 解决移动端弹出键盘导致页面fixed布局错乱的问题
2019/11/06 Javascript
让Vue响应Map或Set的变化操作
2020/11/11 Javascript
30分钟搭建Python的Flask框架并在上面编写第一个应用
2015/03/30 Python
Python遍历文件夹和读写文件的实现方法
2017/05/10 Python
Python3安装Pymongo详细步骤
2017/05/26 Python
如何在python中使用selenium的示例
2017/12/26 Python
python多进程中的内存复制(实例讲解)
2018/01/05 Python
Python实现字符串的逆序 C++字符串逆序算法
2020/05/28 Python
python实现log日志的示例代码
2018/04/28 Python
Python中循环后使用list.append()数据被覆盖问题的解决
2018/07/01 Python
pandas通过索引进行排序的示例
2018/11/16 Python
对python中数据集划分函数StratifiedShuffleSplit的使用详解
2018/12/11 Python
python文件转为exe文件的方法及用法详解
2019/07/08 Python
PyTorch的SoftMax交叉熵损失和梯度用法
2020/01/15 Python
python matplotlib:plt.scatter() 大小和颜色参数详解
2020/04/14 Python
python super()函数的基本使用
2020/09/10 Python
纯css实现照片墙3D效果的示例代码
2017/11/13 HTML / CSS
金讯Java笔试题目
2013/06/18 面试题
python中的class_static的@classmethod的巧妙用法
2021/06/22 Python
电频谱管理的原则是什么
2022/02/18 无线电
Tomcat用户管理的优化配置详解
2022/03/31 Servers
Win11跳过联网界面创建本地管理账户的3种方法
2022/04/20 数码科技