最近在做一个秒杀活动,处于性能和响应速度的考虑,使用了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; } } ?>
如果这事要从根本上解决,请先学习:
1.锁。
2.传统数据库事务。
然后再学习Redis与传统数据库的区别。
用incr/decr 或者 列表来实现。
redis有现存的队列啊, 就跟超市结帐一样, 得一个一个处理, 并发会有问题的
在高并发的情况下,还是使用队列防止超发比较靠谱。依靠watch进行监控是不靠谱的,但是这和watch的位置并没有关系,不是说将get命令放在watch之下就可以解决这个问题了,watch并不是枷锁,在这个地方也不会将并行串行化,这和传统的数据库的锁机制有所不同。
初步断定原因是,并发请求时,存在满足if($rob_result)这个条件,但是并不是所有的客户端都已经进行了watch,这个和redis的单线程机制有关,很有可能,在客户端执行watch命令进入命令队列的时候,有其他客户端的命令先放入到了命令队列中,从而导致超发现象的出现
考虑一下这种情况。
mywatchkey=99
用户A请求mywatchkey得到99,
用户B请求mywatchkey得到99,
当A,B请求完成之后,mywatchkey应该为多少呢。。101,还是100?
我觉得这个代码在高并发的情况,仍然会出现超卖现象。假如:只剩 1个奖品的时候,有三个人同时执行 $redis->watch("mywatchkey") 拿到的数据是 99 ,那么就出现超卖现象了。
由于redis是单线程读取,那么就用最简单的队列实现吧。
在抽奖前先把奖品数量,写入redis队列award:100 // 长度为100的 list ,值只是作为是否中奖
并发抽奖