Heim  >  Artikel  >  Backend-Entwicklung  >  使用redis秒杀出现产品超发现象求解?

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

WBOY
WBOYOriginal
2016-10-17 09:30:081169Durchsuche

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

<code><?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($mywatchkeywatch("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 class="brush:php;toolbar:false">";  
        var_dump($mywatchlist);  
    }else{  
        echo "手气不好,再抢购!";exit;  
    }  
}  
?>  

回复内容:

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

<code><?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($mywatchkeywatch("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 class="brush:php;toolbar:false">";  
        var_dump($mywatchlist);  
    }else{  
        echo "手气不好,再抢购!";exit;  
    }  
}  
?>  

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

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

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

  2. 并发抽奖

<code>$award = $redis->lpop('award:100'); // 由于队列只有100个值,可以确保只有100个人中奖
if(!$award){
    echo "手气不好,再抢购!";exit;  
}

// 剩下就是中奖操作的事情了</code>

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

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

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

首先问一下你这个代码的“超卖”结果是$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

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

1.锁。

2.传统数据库事务。

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

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn