zoukankan      html  css  js  c++  java
  • ab 模拟测试秒杀存在的问题

    1.我的环境是windows下的phpstudy,进入到apache/bin目录里面有个ab.exe,压力测试命令如下

    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);访问量堆积起来即可

    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.如果开启

    1. $sql="update num set num=num-1 where id=1 and (num -1 ) >= 0";  

    经过测试num字段最小为0,在压力测试下代码运行正常,

    想要增大ab压力并发量测试,

    1. ./ab.exe -c 500 -n 1000 http://192.168.1.244/mysql.php  

    会出现如下(看来要去Linux下搭建apache来测试了)

    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压力测试杠杠的,不会报错了

    1. ./ab.exe -c 1000 -n 1000 http://192.168.1.233/redis.php  
    此压力下,redis数据正常,不会出现负数
    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如下:

    1. ./ab.exe -c 2 -n 2 http://192.168.1.233/redis.php  

    修复代码如下,加入watch监听,确保数据准确性

    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个人抢到了,数据很精准
    1. ./ab.exe -c 1000 -n 1000 http://192.168.1.233/redis.php  
    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

    因为 秒杀后库存+订单=秒杀前库存,所以采用也要采用事物来处理

    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.总结,以上只是解决了秒杀时候,超卖的问题,但是并未考虑秒杀效率,只是做了简单测试,后期会加入队列机制处理秒杀


  • 相关阅读:
    sizeof和strlen的区别
    备注
    将一个正整数分解质因数
    malloc和new有什么区别
    用C来实现内存池
    句柄和指针的区别和联系是什么?
    c/c++ 宏中"#"和"##"的用法
    手机上网的原理
    数据类型转换(转自CSDN)
    vc debug和Release的切换
  • 原文地址:https://www.cnblogs.com/wepe/p/7424584.html
Copyright © 2011-2022 走看看