当前位置:首页 > PHP教程 > php高级应用 > 列表

PHP多线程模拟实现秒杀抢单

发布:smiling 来源: PHP粉丝网  添加日期:2018-10-11 12:55:42 浏览: 评论:0 

应集团要求给服务号做了个抢单秒杀的功能,需要对秒杀做个测试,想试试PHP多线程,就模拟了下抢单功能。

先说秒杀模块的思路:

正常情况下的用户秒杀操作

1、发起秒杀请求

2、进入秒杀队列

3、随机滞后 1 - 2 秒进行秒杀结果查询请求(算是变相分流吧)

4、成功则生成订单

5、返回结果

以下是模拟秒杀的代码:

  1. <?php 
  2.   
  3.   
  4. set_time_limit(0); 
  5.   
  6. /** 
  7. * 线程的执行任务 
  8. */ 
  9. class Threadrun extends Thread 
  10. { 
  11.   public $url; 
  12.   public $data; 
  13.   public $params; 
  14.   
  15.   public function __construct($url, $params=[]) 
  16.   { 
  17.    $this->url = $url; 
  18.    $this->params = $params; 
  19.   } 
  20.   
  21.   public function run() 
  22.   { 
  23.    if(($url = $this->url)) 
  24.    { 
  25.      $params = [ 
  26.       'goods_id'  => 1, 
  27.       'activity_id'  => 1, 
  28.       'user_id'   => isset($this->params['user_id']) ? $this->params['user_id'] : $this->getCurrentThreadId(), 
  29.      ]; 
  30.      $startTime = microtime(true); 
  31.      $this->data = [ 
  32.       'id'   => $params['user_id'], 
  33.       'result'  => model_http_curl_get( $url, $params ), 
  34.       'time'  => microtime(true)-$startTime, 
  35.       'now'   => microtime(true), 
  36.      ]; 
  37.    } 
  38.   } 
  39. } 
  40.   
  41. /** 
  42. * 执行多线程 
  43. */ 
  44. function model_thread_result_get($urls_array) 
  45. { 
  46.   foreach ($urls_array as $key => $value) 
  47.   { 
  48.    $threadPool[$key] = new Threadrun($value["url"],['user_id'=>$value['user_id']]); 
  49.    $threadPool[$key]->start(); 
  50.   } 
  51.   foreach ($threadPool as $thread_key => $thread_value) 
  52.   { 
  53.    while($threadPool[$thread_key]->isRunning()) 
  54.    { 
  55.      usleep(10); 
  56.    } 
  57.    if($threadPool[$thread_key]->join()) 
  58.    { 
  59.      $variable_data[$thread_key] = $threadPool[$thread_key]->data; 
  60.    } 
  61.   } 
  62.   return $variable_data; 
  63. } 
  64.   
  65. /** 
  66. * 发送 HTTP 请求 
  67. */ 
  68. function model_http_curl_get($url,$data=[],$userAgent="") 
  69. { 
  70.   $userAgent = $userAgent ? $userAgent : 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.2)'; 
  71.   $curl = curl_init(); 
  72.   curl_setopt($curl, CURLOPT_URL, $url); 
  73.   curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); 
  74.   curl_setopt($curl, CURLOPT_TIMEOUT, 5); 
  75.   curl_setopt($curl, CURLOPT_USERAGENT, $userAgent); 
  76.   curl_setopt($curl, CURLOPT_POST, true); 
  77.   if( !emptyempty($data) ) { 
  78.    curl_setopt($curl, CURLOPT_POSTFIELDS, $data); 
  79.   } 
  80.   $result = curl_exec($curl); 
  81.   curl_close($curl); 
  82.   return $result; 
  83. } 
  84.   
  85.   
  86. /** 
  87.  * 友好的打印变量 
  88.  * @param $val 
  89.  */ 
  90. function dump( $val ) 
  91. { 
  92.   echo '<pre>'; 
  93.   var_dump($val); 
  94.   echo '</pre>'; 
  95. } 
  96.   
  97. /** 
  98.  * 写日志 
  99.  * @param $msg 
  100.  * @param string $logPath 
  101.  */ 
  102. function writeLog( $msg, $logPath='' ) { 
  103.   if( emptyempty($logPath) ) { 
  104.    $logPath = date('Y_m_d').'.log'; 
  105.   } 
  106.   if( !file_exists($logPath) ) { 
  107.    $fp = fopen( $logPath,'w' ); 
  108.    fclose( $fp ); 
  109.   } 
  110.   error_log( $msg.PHP_EOL, 3, $logPath); 
  111. } 
  112.   
  113. /** 
  114.  * 生成日志信息 
  115.  * @param $result 
  116.  * @param $timeDiff 
  117.  * @return bool|string 
  118.  */ 
  119. function createLog( $result, $timeDiff ){ 
  120.   if( emptyempty($result) || !is_array($result) ) { 
  121.    return false; 
  122.   } 
  123.   $succeed = 0; 
  124.   $fail = 0; 
  125.   foreach( $result as $v ) { 
  126.    $times[] = $v['time']; 
  127.    $v['result'] === false ? $fail++ : $succeed++; 
  128.   } 
  129.   $totalTime = array_sum( $times ); 
  130.   $maxTime = max( $times ); 
  131.   $minTime = min( $times ); 
  132.   $sum = count( $times ); 
  133.   $avgTime = $totalTime/$sum; 
  134.   $segment = str_repeat('=',100); 
  135.   $flag = $segment . PHP_EOL; 
  136.   $flag .= '总共执行时间:' . $timeDiff . PHP_EOL ; 
  137.   $flag .= '最大执行时间:' . $maxTime . PHP_EOL; 
  138.   $flag .= '最小执行时间:' . $minTime . PHP_EOL; 
  139.   $flag .= '平均请求时间:' . $avgTime . PHP_EOL; 
  140.   $flag .= '请求数:' . $sum . PHP_EOL; 
  141.   $flag .= '请求成功数:' . $succeed . PHP_EOL; 
  142.   $flag .= '请求失败数:' . $fail . PHP_EOL; 
  143.   $flag .= $segment . PHP_EOL; 
  144.   return $flag; 
  145.   
  146. } 
  147.   
  148.   
  149. /** 
  150.  * 发起秒杀请求 
  151.  */ 
  152. function insertList( $urls, $logPath='' ) 
  153. { 
  154.   $t = microtime(true); 
  155.   $result = model_thread_result_get($urls); 
  156.   $e = microtime(true); 
  157.   $timeDiff = $e-$t; 
  158.   echo "总执行时间:" . $timeDiff . PHP_EOL; 
  159.   foreach( $result as $v ) { 
  160.    $msg = '用户【' . $v['id'] . '】秒杀商品, 返回结果 ' . $v['result'] . ' 用时【' . $v['time'] . ' 秒】 当前时间【'.$v['now'].'】'; 
  161.    writeLog( $msg,$logPath ); 
  162.   } 
  163.   $logStr = createLog( $result, $timeDiff); 
  164.   writeLog( $logStr, $logPath ); 
  165.   return $result; 
  166. } 
  167.   
  168.   
  169. //发起秒杀请求 
  170. for ($i=0; $i < 1000; $i++) 
  171. { 
  172.   $urls_array[] = array("name" => "baidu", "url" => "http://***.***.com/seckill/shopping/listinsert"); 
  173. } 
  174.   
  175. $list = insertList( $urls_array, './inset.log' ); 
  176.   
  177. //发起秒杀结果查询请求 
  178. $urls_array = []; 
  179. foreach( $list as $v ) { 
  180.   if( $v['result'] === false ) { 
  181.    continue; 
  182.   } 
  183.   $urls_array[] = array( 
  184.         "name"  => "baidu", 
  185.         "url"  => "http://***.***.com/seckill/shopping/query", 
  186.         'user_id' => $v['id'], 
  187.   );//phpfensi.com 
  188. } 
  189. insertList( $urls_array, './query.log' ); 

系统测试结果:

模拟 1000 并发的情况,单机每秒 300+ 订单,服务器毫无压力。

反倒是测试机受不了了,CPU 飙升 100%。 Apache 偶尔崩溃。

不知道是 PHP 多线程和 Windows 环境的支持不好,还是 PHP 多线程本身的问题,区区 1000 线程跑不动。多线程的地方还是比较需要 Python 和 C 出马。

Tags: 线程

分享到: