使用redis秒殺出現產品超發現象求解?
阿新 • • 發佈:2019-01-09
最近在做一個秒殺活動,處於效能和響應速度的考慮,使用了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個獎品的時候,有三個人同時執行 $redis->watch("mywatchkey")
拿到的資料是 99 ,那麼就出現超賣現象了。
如果在A、B都得到99以後,A先執行完了,B才開始watch呢
由於redis
是單執行緒讀取,那麼就用最簡單的佇列實現吧。
-
在抽獎前先把獎品數量,寫入
redis
佇列award:100
// 長度為100的 list ,值只是作為是否中獎 -
併發抽獎
$award = $redis->lpop('award:100'); // 由於佇列只有100個值,可以確保只有100個人中獎
if(!$award){
echo "手氣不好,再搶購!";exit;
}
// 剩下就是中獎操作的事情了
親測,用ab 壓測併發500 請求4000 無超賣!
<?php
header("content-type:text/html;charset=utf-8");
$redis = new redis();
$result = $redis->connect('127.0.0.1', 7379);
$redis->watch("mywatchlist");
$len = $redis->hlen("mywatchlist");
$rob_total = 100; //搶購數量
if ($len < $rob_total) {
$redis->multi();
$redis->hSet("mywatchlist", "user_id_" . mt_rand(1, 999999), time());
$rob_result = $redis->exec();
file_put_contents("log.txt", $len . PHP_EOL, FILE_APPEND);
if ($rob_result) {
$mywatchlist = $redis->hGetAll("mywatchlist");
echo "搶購成功!<br/>";
echo "剩餘數量:" . ($rob_total - $len - 1) . "<br/>";
echo "使用者列表:<pre>";
var_dump($mywatchlist);
} else {
echo "手氣不好,再搶購!";
exit;
}
} else {
echo "已賣光!";
exit;
}
?>
Optimistic locking using check-and-set。你似乎呼叫反了,應該是先watch,再get。而且樂觀鎖似乎只控制當你修改的時候是否和你原來獲得的一樣,也就是說在高併發情況下很容易出現一堆錯誤,至於超選不是它控制的。