redis簡單實現高併發秒殺功能
阿新 • • 發佈:2021-02-20
前言:
秒殺功能不外乎就是解決下面兩個問題,
- 第一個是高併發對資料庫產生的壓力,
- 第二個是競爭狀態下如何解決庫存的正確減少,則超賣問題。
使用redis是最優方式,檔案鎖和資料庫鎖都不太好,因為redis可以方便實現分散式鎖,而且redis支援的併發量遠遠大於檔案鎖和資料庫鎖。redis使用樂觀鎖(共享鎖),悲觀鎖(排它鎖)都可以,不過悲觀鎖有個問題就是鎖等待的時間會佔用大量記憶體,秒殺一般是少量的資料,所以是讀多寫少場景,使用樂觀鎖更加合適。另外redis實現悲觀鎖不太友好,會產生一些問題,這些問題需要結合lua指令碼才能解決。使用佇列也可以,但是併發量會使佇列的記憶體瞬間佔慢。
redis使用樂觀鎖非常簡單,就是事務結合watch()方法實現監控,因為悲觀鎖是程序阻塞的,為了使用者體驗更好,可以手動模擬把阻塞改成非阻塞。以下是實現的程式碼:
//初始化redis
$redis = Cache::factory('Redis');
$stock = 3; //搶購貨存數量
$i = 0;
$rebeat = 5; //重複執行次數(如果鎖衝突了,允許重複執行,直到沒有出現鎖衝突為止,把阻塞模擬成非阻塞)
while($i < $rebeat) {
$watchkey = $redis->handle()->get("watchkey");
if(!$watchkey)
$watchkey = 0;
if ($watchkey < $stock) {
//1.監控watchlist,因為雖然redis有原子性,但是事務沒有原子性,所以watch這裡起到一個事務鎖(樂觀鎖)的功能.
//2.舉個例子,假如有兩個併發程序去競爭購買權,redis在監控一個key,第一個先到,第二個後到,先到的搶先一步更新了key,後到的雖然也進來的,但是得知key已被更新,不得已只能中斷自己當前的執行事務。
//3.這裡監控watchlist和watchkey都可以。
$redis->handle()->watch("watchlist");
//$redis->watch("watchkey");
//開啟事務
$redis->handle()->multi();
//插入搶購資料(先放入redis,等搶購完成之後再非同步入庫)
$redis->handle()->hSet("watchlist", "user_id_" . mt_rand(1, 9999), time());
$redis->handle()->set("watchkey", $watchkey + 1);
//執行事務
$result = $redis->handle()->exec();
if ($result) {
$watchlist = $redis->handle()->hGetAll("watchlist");
return array(
'status'=> 1,
'msg'=>'搶購成功,剩餘數量:'.($stock - $watchkey - 1),
'data'=>$watchlist
);
break;
} else {
//設定一個迴圈時間防止效能損耗過大
usleep(5000);
$i++;
}
} else {
return array(
'status'=> 0,
'msg'=>'搶購失敗,商品已經搶購完畢!',
'data'=>[]
);
break;
}
}
return array(
'status'=> 0,
'msg'=>'搶購失敗,鎖衝突了!',
'data'=>[]
);
結果分析:
執行結果如下,使用ab工具模擬多個程序執行也沒有出現超賣情況