您的位置:首页 > 其它

ab 模拟测试秒杀存在的问题

2016-09-06 20:08 309 查看
1.我的环境是windows下的phpstudy,进入到apache/bin目录里面有个ab.exe,压力测试命令如下

[python] view
plain copy

 





./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

 





<?php  

header("Content-type: text/html; charset=utf-8");  

//pdo连接数据库方法  

$dbms='mysql';     //数据库类型  

$host='localhost'; //数据库主机名  

$dbName='test';    //使用的数据库  

$user='root';      //数据库连接用户名  

$pass='root';          //对应的密码  

$dsn="$dbms:host=$host;dbname=$dbName";  

try {  

    $dbh = new PDO($dsn, $user, $pass, array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'utf8';")); //初始化一个PDO对象  

    echo "连接成功<br/>";  

    $sq="select num from num where id=1";  

    $rs=$dbh->query($sq);  

    $rs->setFetchMode(PDO::FETCH_ASSOC);  

    $count = $rs->fetch();  

    print_r($count);  

    //sleep(2); 如果压力不够释放此行代码,让访问量堆积,数据库num字段成负数  

    if($count['num']>0){  

        //$sql="update num set num=num-1 where id=1 and (num -1 ) >= 0";//开启此行,注释下行,无法破  

        $sql="update num set num=num-1 where id=1";  

          

        echo $sql;  

        $count = $dbh->exec($sql);  

  

                $sql2="insert into goods_order (goods_id,user_id) values(1,123456789)";  

        echo $sql2;  

        $count2 = $dbh->exec($sql2);  

  

        echo "购买成功<br />";  

    }  

    $dbh = null;  

} catch (PDOException $e) {  

    die ("Error!: " . $e->getMessage() . "<br/>");  

}  

3.如果开启

[php] view
plain copy

 





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

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

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

[python] view
plain copy

 





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

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

[python] view
plain copy

 





This is ApacheBench, Version 2.3 <$Revision: 1706008 $>  

Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/  

Licensed to The Apache Software Foundation, http://www.apache.org/  

  

Benchmarking 192.168.1.244 (be patient)  

Completed 100 requests  

Total of 125 requests completed  

  

Test aborted after 10 failures  

  

apr_socket_connect(): ▒▒▒▒Ŀ▒▒▒▒▒▒▒▒▒▒▒ܾ▒▒▒▒޷▒▒▒▒ӡ▒   (730061)  

4.redis测试,搭建好linux下的apache后,访问linux下的redis.php压力测试杠杠的,不会报错了

[python] view
plain copy

 





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

此压力下,redis数据正常,不会出现负数

[php] view
plain copy

 





<?php  

header("content-type:text/html;charset=utf-8");  

$redis = new redis();  

$result = $redis->connect('192.168.1.233',"6379");  

//$num = $redis->set("num",5);  

//die;  

$num = ($redis->get("num"));  

$count=(int)$num;  

echo "总共有:".$count;  

echo "<br/>";  

if($count>0){  

    //sleep(2);  

    $redis->set("num",$num-1);  

}else{  

}  

var_dump($num);  

?>  

5.以上是ab测试,现在分析代码

先分析redis.php,上面的这个实现在只有一个客户端的时候可以执行得很好。 但是, 当多个客户端同时对同一个键进行这样的操作时, 就会产生竞争条件。举个例子, 如果客户端 A 和 B 都读取了键原来的值, 比如 2, 那么两个客户端都会将键的值设为 1 , 但正确的结果应该是 0 才对。

有了 WATCH , 我们就可以轻松地解决这类问题了:

因为redis的性能很高,当num为2时,ab模拟两个并发量后num为1,模拟两个并发量和watch如下:

[python] view
plain copy

 





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

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

[php] view
plain copy

 





<?php  

//set('num');可以在终端执行  

header("content-type:text/html;charset=utf-8");  

$redis = new redis();  

$result = $redis->connect('192.168.1.233',"6379");  

$redis->watch("num");  

$num = ($redis->get("num"));  

$redis->multi();  

$count=(int)$num;  

echo "总共有:".$count;  

echo "<br/>";  

if($count>0){  

    //sleep(2);  

    $redis->set("num",$num-1);  

    $redis->incr("order");  

    $exec = $redis->exec();  

    //var_dump($exec);  

    //die;  

    if($exec[0]==true){  

        echo "抢购成功,还剩:".($count-1);  

    }else{  

        echo "很不幸,没抢到,可以再抢一把";  

    }  

}else{  

    echo "活动结束";  

}  

    //var_dump($num);  

    //$redis->close();  

?>  

设置1000个库存,让1000个人去抢,196个人抢到了,数据很精准

[python] view
plain copy

 





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

[python] view
plain copy

 





192.168.1.233:6379> set num 1000  

OK  

192.168.1.233:6379> get num  

"804"  

192.168.1.233:6379> get order  

"196"  

6.分析完redis.php,我们来分析mysql.php

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

[php] view
plain copy

 





<?php  

header("Content-type: text/html; charset=utf-8");  

//pdo连接数据库方法  

$dbms='mysql';     //数据库类型  

$host='localhost'; //数据库主机名  

$dbName='test';    //使用的数据库  

$user='root';      //数据库连接用户名  

$pass='root';          //对应的密码  

$dsn="$dbms:host=$host;dbname=$dbName";  

try {  

    $dbh = new PDO($dsn, $user, $pass, array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'utf8';")); //初始化一个PDO对象  

    echo "连接成功<br/>";  

    $sq="select num from num where id=1";  

    $rs=$dbh->query($sq);  

    $rs->setFetchMode(PDO::FETCH_ASSOC);  

    $count = $rs->fetch();  

    print_r($count);  

    //sleep(2);  

    if($count['num']>0){   

        try{  

            $dbh->beginTransaction(); // 开启一个事务    

            //$row = null;    

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

            $row = $dbh->exec($sql); // 执行第一个 SQL              

            if (!$row)     

                throw new PDOException('抢购失败,再抢'); // 如出现异常提示信息或执行动作    

            $sql2="insert into goods_order (goods_id,user_id) values(1,123456789)";  

            $row = $dbh->exec($sql2); // 执行第二个 SQL    

            if (!$row)     

                throw new PDOException('抢购失败,再抢');    

            $dbh->commit();    

  

        }catch(PDOException $ex){  

             $dbh->rollback(); // 执行失败,事务回滚    

            exit($ex->getMessage());    

        }  

    }  

    $dbh = null;  

} catch (PDOException $e) {  

    die ("Error!: " . $e->getMessage() . "<br/>");  

}  

7.总结,以上只是解决了秒杀时候,超卖的问题,但是并未考虑秒杀效率,只是做了简单测试,后期会加入队列机制处理秒杀
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: