search

Home  >  Q&A  >  body text

php - 使用redis秒杀出现产品超发现象求解?

最近在做一个秒杀活动,处于性能和响应速度的考虑,使用了redis。写的时候就特别注意了杜绝超发现象,基于redis理论的cas(check and set)乐观锁,想着应该能够杜绝该问题,但是还是出现了,很疑惑求大神帮助,具体的代码大致如下:

<?php  
header("content-type:text/html;charset=utf-8");  
$redis = new redis();  
$result = $redis->connect('10.10.10.119', 6379);  
$mywatchkey = $redis->get("mywatchkey");  
$rob_total = 100;   //抢购数量  
if($mywatchkey<$rob_total){  
    $redis->watch("mywatchkey");  
    $redis->multi();  
      
    //设置延迟,方便测试效果。  
    sleep(5);  
    //插入抢购数据  
    $redis->hSet("mywatchlist","user_id_".mt_rand(1, 9999),time());  
    $redis->set("mywatchkey",$mywatchkey+1);  
    $rob_result = $redis->exec();  
    if($rob_result){  
        $mywatchlist = $redis->hGetAll("mywatchlist");  
        echo "抢购成功!<br/>";  
        echo "剩余数量:".($rob_total-$mywatchkey-1)."<br/>";  
        echo "用户列表:<pre>";  
        var_dump($mywatchlist);  
    }else{  
        echo "手气不好,再抢购!";exit;  
    }  
}  
?>  
PHP中文网PHP中文网2817 days ago825

reply all(11)I'll reply

  • 伊谢尔伦

    伊谢尔伦2017-04-10 17:35:47

    我觉得这个代码在高并发的情况,仍然会出现超卖现象。假如:只剩 1个奖品的时候,有三个人同时执行 $redis->watch("mywatchkey") 拿到的数据是 99 ,那么就出现超卖现象了。

    由于redis是单线程读取,那么就用最简单的队列实现吧。

    1. 在抽奖前先把奖品数量,写入redis队列award:100 // 长度为100的 list ,值只是作为是否中奖

    2. 并发抽奖

    $award = $redis->lpop('award:100'); // 由于队列只有100个值,可以确保只有100个人中奖
    if(!$award){
        echo "手气不好,再抢购!";exit;  
    }
    
    // 剩下就是中奖操作的事情了

    reply
    0
  • 黄舟

    黄舟2017-04-10 17:35:47

    redis 用错了.并发要用 redis 的 list 队列类型.不要在代码里做控制

    reply
    0
  • 迷茫

    迷茫2017-04-10 17:35:47

    sleep(5);
    你公司还收不收人?

    reply
    0
  • 迷茫

    迷茫2017-04-10 17:35:47

    考虑一下这种情况。
    mywatchkey=99
    用户A请求mywatchkey得到99,
    用户B请求mywatchkey得到99,
    当A,B请求完成之后,mywatchkey应该为多少呢。。101,还是100?

    reply
    0
  • 巴扎黑

    巴扎黑2017-04-10 17:35:47

    你这个是经典的Check-then-Act错误

    reply
    0
  • 伊谢尔伦

    伊谢尔伦2017-04-10 17:35:47

    首先问一下你这个代码的“超卖”结果是$mywatchkey > 100了 (我认为不可能出现)
    还是$mywatchkey =100时候同时有两个页面出现“抢购成功!“
    我没有对这个代码进行测试,我只是猜测会不会是以下的情况:

    1.Session A : 进行mywatchkey检查,此时mywatchkey = 99

    2.Session B : 处理完 mywatchkey以后,此时mywatchkey=100 但是并不影响Session A的后续操作

    补充两个连接,看看吧,或许有收获

    transactions-and-watch-statement-in-redis
    redis-watch-multi-exec-by-one-client

    reply
    0
  • 天蓬老师

    天蓬老师2017-04-10 17:35:47

    如果这事要从根本上解决,请先学习:

    1.锁。

    2.传统数据库事务。

    然后再学习Redis与传统数据库的区别。

    reply
    0
  • PHP中文网

    PHP中文网2017-04-10 17:35:47

    在高并发的情况下,还是使用队列防止超发比较靠谱。依靠watch进行监控是不靠谱的,但是这和watch的位置并没有关系,不是说将get命令放在watch之下就可以解决这个问题了,watch并不是枷锁,在这个地方也不会将并行串行化,这和传统的数据库的锁机制有所不同。

    初步断定原因是,并发请求时,存在满足if($rob_result)这个条件,但是并不是所有的客户端都已经进行了watch,这个和redis的单线程机制有关,很有可能,在客户端执行watch命令进入命令队列的时候,有其他客户端的命令先放入到了命令队列中,从而导致超发现象的出现

    reply
    0
  • 阿神

    阿神2017-04-10 17:35:47

    还不如用redis队列

    reply
    0
  • 巴扎黑

    巴扎黑2017-04-10 17:35:47

    redis有现存的队列啊, 就跟超市结帐一样, 得一个一个处理, 并发会有问题的

    reply
    0
  • Cancelreply