ab 模擬測試秒殺存在的問題

1.我的環境是windows下的phpstudy,進入到apache/bin目錄裏面有個ab.exe,壓力測試命令如下

[python] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. ./ab.exe -c 200 -n 1000 http://192.168.1.244/mysql.php  

2.mysql.php代碼如下

正常的邏輯思維,壓力增大後,導致數據庫num字段成爲負數,將下面代碼粘貼到自己網站下測試即可。

在test數據庫下,新建一個num(庫存)的表,id字段int類型主鍵自增,num字段int類型

新建一個goods_order(訂單)的表,id字段int主鍵自增,goods_id字段int,user_id字段int類型,

如果發現num字段無法成爲負數,打開sleep(2);訪問量堆積起來即可

[php] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. <?php  
  2. header("Content-type: text/html; charset=utf-8");  
  3. //pdo連接數據庫方法  
  4. $dbms='mysql';     //數據庫類型  
  5. $host='localhost'//數據庫主機名  
  6. $dbName='test';    //使用的數據庫  
  7. $user='root';      //數據庫連接用戶名  
  8. $pass='root';          //對應的密碼  
  9. $dsn="$dbms:host=$host;dbname=$dbName";  
  10. try {  
  11.     $dbh = new PDO($dsn$user$passarray(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'utf8';")); //初始化一個PDO對象  
  12.     echo "連接成功<br/>";  
  13.     $sq="select num from num where id=1";  
  14.     $rs=$dbh->query($sq);  
  15.     $rs->setFetchMode(PDO::FETCH_ASSOC);  
  16.     $count = $rs->fetch();  
  17.     print_r($count);  
  18.     //sleep(2); 如果壓力不夠釋放此行代碼,讓訪問量堆積,數據庫num字段成負數  
  19.     if($count['num']>0){  
  20.         //$sql="update num set num=num-1 where id=1 and (num -1 ) >= 0";//開啓此行,註釋下行,無法破  
  21.         $sql="update num set num=num-1 where id=1";  
  22.           
  23.         echo $sql;  
  24.         $count = $dbh->exec($sql);  
  25.   
  26.                 $sql2="insert into goods_order (goods_id,user_id) values(1,123456789)";  
  27.         echo $sql2;  
  28.         $count2 = $dbh->exec($sql2);  
  29.   
  30.         echo "購買成功<br />";  
  31.     }  
  32.     $dbh = null;  
  33. } catch (PDOException $e) {  
  34.     die ("Error!: " . $e->getMessage() . "<br/>");  
  35. }  

3.如果開啓

[php] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. $sql="update num set num=num-1 where id=1 and (num -1 ) >= 0";  

經過測試num字段最小爲0,在壓力測試下代碼運行正常,

想要增大ab壓力併發量測試,

[python] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. ./ab.exe -c 500 -n 1000 http://192.168.1.244/mysql.php  

會出現如下(看來要去linux下搭建apache來測試了)

[python] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. This is ApacheBench, Version 2.3 <$Revision: 1706008 $>  
  2. Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/  
  3. Licensed to The Apache Software Foundation, http://www.apache.org/  
  4.   
  5. Benchmarking 192.168.1.244 (be patient)  
  6. Completed 100 requests  
  7. Total of 125 requests completed  
  8.   
  9. Test aborted after 10 failures  
  10.   
  11. apr_socket_connect(): ▒▒▒▒Ŀ▒▒▒▒▒▒▒▒▒▒▒ܾ▒▒▒▒޷▒▒▒▒ӡ▒   (730061)  

4.redis測試,搭建好linux下的apache後,訪問linux下的redis.PHP壓力測試槓槓的,不會報錯了

[python] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. ./ab.exe -c 1000 -n 1000 http://192.168.1.233/redis.php  
此壓力下,redis數據正常,不會出現負數

[php] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. <?php  
  2. header("content-type:text/html;charset=utf-8");  
  3. $redis = new redis();  
  4. $result = $redis->connect('192.168.1.233',"6379");  
  5. //$num = $redis->set("num",5);  
  6. //die;  
  7. $num = ($redis->get("num"));  
  8. $count=(int)$num;  
  9. echo "總共有:".$count;  
  10. echo "<br/>";  
  11. if($count>0){  
  12.     //sleep(2);  
  13.     $redis->set("num",$num-1);  
  14. }else{  
  15. }  
  16. var_dump($num);  
  17. ?>  


5.以上是ab測試,現在分析代碼

先分析redis.php,上面的這個實現在只有一個客戶端的時候可以執行得很好。 但是, 當多個客戶端同時對同一個鍵進行這樣的操作時, 就會產生競爭條件。舉個例子, 如果客戶端 A 和 B 都讀取了鍵原來的值, 比如 2, 那麼兩個客戶端都會將鍵的值設爲 1 , 但正確的結果應該是 0 纔對。

有了 WATCH , 我們就可以輕鬆地解決這類問題了:

因爲redis的性能很高,當num爲2時,ab模擬兩個併發量後num爲1,模擬兩個併發量和watch如下:

[python] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. ./ab.exe -c 2 -n 2 http://192.168.1.233/redis.php  

修復代碼如下,加入watch監聽,確保數據準確性

[php] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. <?php  
  2. //set('num');可以在終端執行  
  3. header("content-type:text/html;charset=utf-8");  
  4. $redis = new redis();  
  5. $result = $redis->connect('192.168.1.233',"6379");  
  6. $redis->watch("num");  
  7. $num = ($redis->get("num"));  
  8. $redis->multi();  
  9. $count=(int)$num;  
  10. echo "總共有:".$count;  
  11. echo "<br/>";  
  12. if($count>0){  
  13.     //sleep(2);  
  14.     $redis->set("num",$num-1);  
  15.     $redis->incr("order");  
  16.     $exec = $redis->exec();  
  17.     //var_dump($exec);  
  18.     //die;  
  19.     if($exec[0]==true){  
  20.         echo "搶購成功,還剩:".($count-1);  
  21.     }else{  
  22.         echo "很不幸,沒搶到,可以再搶一把";  
  23.     }  
  24. }else{  
  25.     echo "活動結束";  
  26. }  
  27.     //var_dump($num);  
  28.     //$redis->close();  
  29. ?>  

設置1000個庫存,讓1000個人去搶,196個人搶到了,數據很精準

[python] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. ./ab.exe -c 1000 -n 1000 http://192.168.1.233/redis.php  

[python] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. 192.168.1.233:6379> set num 1000  
  2. OK  
  3. 192.168.1.233:6379> get num  
  4. "804"  
  5. 192.168.1.233:6379> get order  
  6. "196"  


6.分析完redis.php,我們來分析MySQL.php

因爲 秒殺後庫存+訂單=秒殺前庫存,所以採用也要採用事物來處理

[php] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. <?php  
  2. header("Content-type: text/html; charset=utf-8");  
  3. //pdo連接數據庫方法  
  4. $dbms='mysql';     //數據庫類型  
  5. $host='localhost'//數據庫主機名  
  6. $dbName='test';    //使用的數據庫  
  7. $user='root';      //數據庫連接用戶名  
  8. $pass='root';          //對應的密碼  
  9. $dsn="$dbms:host=$host;dbname=$dbName";  
  10. try {  
  11.     $dbh = new PDO($dsn$user$passarray(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'utf8';")); //初始化一個PDO對象  
  12.     echo "連接成功<br/>";  
  13.     $sq="select num from num where id=1";  
  14.     $rs=$dbh->query($sq);  
  15.     $rs->setFetchMode(PDO::FETCH_ASSOC);  
  16.     $count = $rs->fetch();  
  17.     print_r($count);  
  18.     //sleep(2);  
  19.     if($count['num']>0){   
  20.         try{  
  21.             $dbh->beginTransaction(); // 開啓一個事務    
  22.             //$row = null;    
  23.             $sql="update num set num=num-1 where id=1 and (num -1 ) >= 0";  
  24.             $row = $dbh->exec($sql); // 執行第一個 SQL              
  25.             if (!$row)     
  26.                 throw new PDOException('搶購失敗,再搶'); // 如出現異常提示信息或執行動作    
  27.             $sql2="insert into goods_order (goods_id,user_id) values(1,123456789)";  
  28.             $row = $dbh->exec($sql2); // 執行第二個 SQL    
  29.             if (!$row)     
  30.                 throw new PDOException('搶購失敗,再搶');    
  31.             $dbh->commit();    
  32.   
  33.         }catch(PDOException $ex){  
  34.              $dbh->rollback(); // 執行失敗,事務回滾    
  35.             exit($ex->getMessage());    
  36.         }  
  37.     }  
  38.     $dbh = null;  
  39. } catch (PDOException $e) {  
  40.     die ("Error!: " . $e->getMessage() . "<br/>");  
  41. }  


7.總結,以上只是解決了秒殺時候,超賣的問題,但是並未考慮秒殺效率,只是做了簡單測試,後期會加入隊列機制處理秒殺

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章