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 相关文章推荐
dedecms 制作模板中使用的全局标记图文教程
Mar 11 PHP
配置Apache2.2+PHP5+CakePHP1.2+MySQL5运行环境
Apr 25 PHP
通过PHP CLI实现简单的数据库实时监控调度
Jul 01 PHP
php自定义函数之递归删除文件及目录
Aug 08 PHP
使用PHP静态变量当缓存的方法
Nov 13 PHP
不使用php api函数实现数组的交换排序示例
Apr 13 PHP
PHP文件上传判断file是否己选择上传文件的方法
Nov 10 PHP
浅谈本地WAMP环境的搭建
May 13 PHP
php简单实现数组分页的方法
Apr 30 PHP
PHP设计模式之工厂模式(Factory Pattern)的讲解
Mar 21 PHP
利用PHP内置SERVER开启web服务(本地开发使用)
Jan 22 PHP
如何通过Apache在本地配置多个虚拟主机
Jul 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字符串函数系列之nl2br(),在字符串中的每个新行 (\n) 之前插入 HTML 换行符br
2011/11/10 PHP
php警告Creating default object from empty value 问题的解决方法
2014/04/02 PHP
PHP中header函数的用法及其注意事项详解
2016/06/13 PHP
简单谈谈PHP面向对象之标识对象
2017/06/27 PHP
PHP 7.4 新语法之箭头函数实例详解
2019/05/09 PHP
jQuery的三种$()
2009/12/30 Javascript
javascript 二分法(数组array)
2010/04/24 Javascript
JavaScript判断窗口是否最小化的代码(跨浏览器)
2010/08/01 Javascript
一个轻量级的javascript库 pj介绍
2010/12/19 Javascript
对setInterval在火狐和chrome切换标签产生奇怪的效果之探索,与解决方案!
2011/10/29 Javascript
JS+CSS实现可拖动的弹出提示框
2015/02/16 Javascript
JavaScript SHA512加密算法详细代码
2016/10/06 Javascript
Vue.js中关于侦听器(watch)的高级用法示例
2018/05/02 Javascript
nodejs通过钉钉群机器人推送消息的实现代码
2019/05/05 NodeJs
中高级前端必须了解的JS中的内存管理(推荐)
2019/07/04 Javascript
python使用两种发邮件的方式smtp和outlook示例
2017/06/02 Python
Python对列表中的各项进行关联详解
2017/08/15 Python
python中 logging的使用详解
2017/10/25 Python
python使用openpyxl库修改excel表格数据方法
2018/05/03 Python
浅谈tensorflow中几个随机函数的用法
2018/07/27 Python
一篇文章搞定Python操作文件与目录
2019/08/13 Python
Python调用scp向服务器上传文件示例
2019/12/22 Python
python+adb+monkey实现Rom稳定性测试详解
2020/04/23 Python
影视制作岗位职责
2013/12/04 职场文书
工厂实习感言
2014/01/14 职场文书
军训鉴定表自我鉴定
2014/02/13 职场文书
教师师德反思材料
2014/02/15 职场文书
付款委托书范本
2014/04/04 职场文书
抗洪抢险事迹材料
2014/05/06 职场文书
2014社会治安综合治理工作总结
2014/12/04 职场文书
2014年学校德育工作总结
2014/12/05 职场文书
总经理岗位职责范本
2015/04/01 职场文书
创业计划书之闲置物品置换中心
2019/12/25 职场文书
快速学习Oracle触发器和游标
2021/06/30 Oracle
MySQL创建管理HASH分区
2022/04/13 MySQL
SpringBoot全局异常处理方案分享
2022/05/25 Java/Android