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 相关文章推荐
第五节 克隆 [5]
Oct 09 PHP
php xml文件操作代码(一)
Mar 20 PHP
php中关于普通表单多文件上传的处理方法
Mar 25 PHP
域名和cookie问题(域名后缀)
Oct 10 PHP
Apache实现Web Server负载均衡详解(不考虑Session版)
Jul 05 PHP
PHP计算一年多少个星期和每周的开始和结束日期
Jul 01 PHP
php实现获取及设置用户访问页面语言类
Sep 24 PHP
php5.4以上版本GBK编码下htmlspecialchars输出为空问题解决方法汇总
Apr 03 PHP
php解析字符串里所有URL地址的方法
Apr 03 PHP
在CentOS上搭建LAMP+vsftpd环境的简单指南
Aug 01 PHP
PHP设计模式之装饰器模式定义与用法详解
Apr 02 PHP
PHP压缩图片功能的介绍
Mar 21 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
Ajax+PHP边学边练 之五 图片处理
2009/12/03 PHP
js 键盘记录实现(兼容FireFox和IE)
2010/02/07 Javascript
javascript时间函数基础介绍
2013/03/28 Javascript
Jquery实现$.fn.extend和$.extend函数
2016/04/14 Javascript
javascript Promise简单学习使用方法小结
2016/05/17 Javascript
详解nodeJS中读写文件方法的区别
2017/03/06 NodeJs
微信小程序 实现点击添加移除class
2017/06/12 Javascript
Iview Table组件中各种组件扩展的使用
2018/10/20 Javascript
eslint 的三大通用规则详解
2019/05/16 Javascript
JS模拟浏览器实现全局搜索功能
2019/09/11 Javascript
浅谈layui框架自带分页和表格重载的接口解析问题
2019/09/11 Javascript
微信小程序实现横向滚动导航栏效果
2019/12/12 Javascript
小程序自动化测试的示例代码
2020/08/11 Javascript
python查询sqlite数据表的方法
2015/05/08 Python
深入讲解Python编程中的字符串
2015/10/14 Python
Python简单实现两个任意字符串乘积的方法示例
2018/04/12 Python
python实现换位加密算法的示例
2018/10/14 Python
python 提取tuple类型值中json格式的key值方法
2018/12/31 Python
使用Template格式化Python字符串的方法
2019/01/22 Python
djang常用查询SQL语句的使用代码
2019/02/15 Python
python 通过可变参数计算n个数的乘积方法
2019/06/13 Python
windows中安装Python3.8.0的实现方法
2019/11/19 Python
Python3操作MongoDB增册改查等方法详解
2020/02/10 Python
Python requests模块session代码实例
2020/04/14 Python
python如何解析复杂sql,实现数据库和表的提取的实例剖析
2020/05/15 Python
如何导出python安装的所有模块名称和版本号到文件中
2020/06/05 Python
Python读取多列数据以及用matplotlib制作图表方法实例
2020/09/23 Python
python实现简单贪吃蛇游戏
2020/09/29 Python
基于注解实现 SpringBoot 接口防刷的方法
2021/03/02 Python
酒店开业庆典主持词
2014/03/21 职场文书
2015年设计师个人工作总结
2015/04/25 职场文书
年会主持人开场白台词
2015/05/29 职场文书
告诉你一个秘密:富人致富的五大优点
2019/07/11 职场文书
详解Python为什么不用设计模式
2021/06/24 Python
Python中的变量与常量
2021/11/11 Python
SQL Server使用PIVOT与unPIVOT实现行列转换
2022/05/25 SQL Server