模拟测试秒杀

本文通过使用Apache Bench工具对PHP和Redis应用进行并发压力测试,展示了如何重现数据库中库存数量变为负数的问题,并介绍了如何利用Redis的WATCH机制来保证数据的一致性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

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

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

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

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

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

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

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

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

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

<?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如下:

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

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

    <?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();  
    ?>  

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

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

    192.168.1.233:6379> set num 100  
    OK  
    192.168.1.233:6379> get num  
    "0"  
    192.168.1.233:6379> get order  
    "100" 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值