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 17 PHP
PHP+MYSQL会员系统的登陆即权限判断实现代码
Sep 23 PHP
php在文件指定行中写入代码的方法
May 23 PHP
php htmlspecialchars()与shtmlspecialchars()函数的深入分析
Jun 05 PHP
PHP针对JSON操作实例分析
Jan 12 PHP
PHP中开启gzip压缩的2种方法
Jan 31 PHP
php模拟post提交数据的方法
Feb 12 PHP
php去除字符串中空字符的常用方法小结
Mar 17 PHP
php根据数据id自动生成编号的实现方法
Oct 16 PHP
PHP读取大文件的几种方法介绍
Oct 27 PHP
PHP开发之归档格式phar文件概念与用法详解【创建,使用,解包还原提取】
Nov 17 PHP
laravel实现前后台路由分离的方法
Oct 13 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
模仿OSO的论坛(五)
2006/10/09 PHP
理解php原理的opcodes(操作码)
2010/10/26 PHP
PHP中的一些常用函数收集
2015/05/26 PHP
[原创]php求圆周率的简单实现方法
2016/05/30 PHP
了解一点js的Eval函数
2012/07/26 Javascript
使用Grunt.js管理你项目的应用说明
2013/04/24 Javascript
offsetHeight在OnLoad中获取为0的现象
2013/07/22 Javascript
读取input:file的路径并显示本地图片的方法
2013/09/23 Javascript
js格式化金额可选是否带千分位以及保留精度
2014/01/28 Javascript
全面了解javascript三元运算符
2016/06/27 Javascript
利用python分析access日志的方法
2016/10/26 Javascript
js判断用户是输入的地址请求的路径(实例讲解)
2017/07/18 Javascript
10 种最常见的 Javascript 错误(频率最高)
2018/02/08 Javascript
解决jquery有正确返回值但不执行success函数的问题
2018/08/20 jQuery
vue实现鼠标移入移出事件代码实例
2019/03/27 Javascript
[03:46]DOTA2英雄基础教程 维萨吉
2013/12/11 DOTA
python网络编程学习笔记(三):socket网络服务器
2014/06/09 Python
Python列表list数组array用法实例解析
2014/10/28 Python
零基础写python爬虫之打包生成exe文件
2014/11/06 Python
Python随手笔记之标准类型内建函数
2015/12/02 Python
Python 正则表达式入门(中级篇)
2016/12/07 Python
python DataFrame获取行数、列数、索引及第几行第几列的值方法
2018/04/08 Python
python3利用Socket实现通信的方法示例
2019/05/06 Python
使用Python-OpenCV消除图像中孤立的小区域操作
2020/07/05 Python
HTML5 b和i标记将被赋予真正的语义
2009/07/16 HTML / CSS
英语专业毕业个人求职自荐信
2013/09/21 职场文书
机械设计及其自动化专业推荐信
2013/10/31 职场文书
生产管理的三大手法
2013/11/11 职场文书
教师个人鉴定材料
2014/02/08 职场文书
车队司机个人自我鉴定
2014/04/17 职场文书
公司领导班子对照检查材料
2014/09/24 职场文书
盗窃罪辩护词范文
2015/05/21 职场文书
2016八一建军节慰问信
2015/11/30 职场文书
爱岗敬业先进典型事迹材料(2016推荐版)
2016/02/26 职场文书
go语言-在mac下brew升级golang
2021/04/25 Golang
Python 多线程处理任务实例
2021/11/07 Python