最近在做一個秒殺活動,處於性能和響應速度的考慮,使用了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($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 class="brush:php;toolbar:false">"; var_dump($mywatchlist); }else{ echo "手气不好,再抢购!";exit; } } ?> </code>
最近在做一個秒殺活動,處於性能和響應速度的考慮,使用了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($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 class="brush:php;toolbar:false">"; var_dump($mywatchlist); }else{ echo "手气不好,再抢购!";exit; } } ?> </code>
我覺得這個程式碼在高並發的情況,還是會出現超賣現象。假如:只剩下 1個獎品的時候,有三個人同時執行 $redis->watch("mywatchkey")
拿到的資料是 99 ,那麼就出現超賣現象了。
由於redis
是單執行緒讀取,那就用最簡單的佇列實作吧。
在抽獎前先把獎品數量,寫入redis
隊列award:100
// 長度為100的 list ,值只是作為是否中獎
並發抽獎
<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與傳統資料庫的差別。