redis實現(xiàn)高并發(fā)下的搶購/秒殺功能

搶購/秒殺是如今很常見的一個應(yīng)用場景诗充,那么高并發(fā)競爭下如何解決超搶(或超賣庫存不足為負(fù)數(shù)的問題)呢?

常規(guī)寫法:

查詢出對應(yīng)商品的庫存诱建,看是否大于0蝴蜓,然后執(zhí)行生成訂單等操作,但是在判斷庫存是否大于0處涂佃,如果在高并發(fā)下就會有問題励翼,導(dǎo)致庫存量出現(xiàn)負(fù)數(shù)

這里我就只談redis的解決方案吧...
我們先來看以下代碼(這里我以laravel為例吧)是否能正確解決超搶/賣的問題:

<?php
 
 $num = 10;   //系統(tǒng)庫存量
 $user_id =  \Session::get('user_id');//當(dāng)前搶購用戶id
 $len = \Redis::llen('order:1');  //檢查庫存蜈敢,order:1 定義為健名
 if($len >= $num)
   return '已經(jīng)搶光了哦';

$result = \Redis::lpush('order:1',$user_id);  //把搶到的用戶存入到列表中
if($result)
  return '恭喜您!搶到了哦';

如果代碼正常運(yùn)行,按照預(yù)期理解的是列表order:1中最多只能存儲10個用戶的id汽抚,因為庫存只有10個抓狭。
然而,但是,在使用jmeter工具模擬多用戶并發(fā)請求時造烁,最后發(fā)現(xiàn)order:1中總是超過5個用戶镐侯,也就是出現(xiàn)了“超搶/超賣”瘦麸。
分析問題就出在這一段代碼:

$len = \Redis::llen('order:1');  //檢查庫存,order:1 定義為健名
if($len >= $num)
   return '已經(jīng)搶光了哦';

在搶購進(jìn)行到一定程度,假如現(xiàn)在已經(jīng)有9個人搶購成功祟身,又來了3個用戶同時搶購,這時if條件將會被繞過(條件同時被滿足了)箩溃,這三個用戶都能搶購成功拉庶。而實際上只剩下一件庫存可以搶了。
在高并發(fā)下木缝,很多看似不大可能是問題的便锨,都成了實際產(chǎn)生的問題了。要解決“超搶/超賣”的問題我碟,核心在于保證檢查庫存時的操作是依次執(zhí)行的放案,再形象的說就是把“多線程”轉(zhuǎn)成“單線程”。即使有很多用戶同時到達(dá)矫俺,也是一個個檢查并給與搶購資格吱殉,一旦庫存搶盡,后面的用戶就無法繼續(xù)了厘托。
我們需要使用redis的原子操作來實現(xiàn)這個“單線程”友雳。首先我們把庫存存在goods_store:1這個列表中,假設(shè)有10件庫存催烘,就往列表中push10個數(shù)沥阱,這個數(shù)沒有實際意義,僅僅只是代表一件庫存伊群。搶購開始后考杉,每到來一個用戶,就從goods_store:1中pop一個數(shù)舰始,表示用戶搶購成功崇棠。當(dāng)列表為空時,表示已經(jīng)被搶光了丸卷。因為列表的pop操作是原子的枕稀,即使有很多用戶同時到達(dá),也是依次執(zhí)行的。搶購的示例代碼如下:
比如這里我先把庫存(可用庫存,這里我強(qiáng)調(diào)下哈,一般都是商品詳情頁搶購,后來者進(jìn)來看到的庫存可能不再是后臺系統(tǒng)配置的10個庫存數(shù)了)放入redis隊列:

 $num=10; //庫存
 $len=\Redis::llen('goods_store:1'); //檢查庫存,goods_store:1 定義為健名
 $count = $num-$len; //實際庫存-被搶購的庫存 = 剩余可用庫存
 for($i=0;$i<$count;$i++)
   \Redis::lpush('goods_store:1',1);//往goods_store列表中,未搶購之前這里應(yīng)該是默認(rèn)滴push10個庫存數(shù)了

 //echo \Redis::llen('goods_store:1');//未搶購之前這里就是10了

好吧萎坷,搶購時間到了:

 /* 模擬搶購操作,搶購前判斷redis隊列庫存量 */
 $count=\Redis::lpop('goods_store:1');//lpop是移除并返回列表的第一個元素凹联。
 if(!$count)
    return '已經(jīng)搶光了哦';
 /* 下面處理搶購成功流程 */
\DB::table('goods')->decrement('num', 1);//減少num庫存字段

用戶搶購成功后,上面的我們也可以稍微優(yōu)化下哆档,比如我們可用將用戶ID存入了order:1列表中蔽挠。接下來我們可以引導(dǎo)這些用戶去完成訂單的其他步驟,到這里才涉及到與數(shù)據(jù)庫的交互瓜浸。最終只有很少的人走到這一步吧澳淑,也就解決的數(shù)據(jù)庫的壓力問題。
我們再改下上面的代碼:

$user_id =  \Session::get('user_id');//當(dāng)前搶購用戶id
/* 模擬搶購操作,搶購前判斷redis隊列庫存量 */
$count=\Redis::lpop('goods_store:1');
if(!$count)
  return '已經(jīng)搶光了哦';

$result = \Redis::lpush('order:1',$user_id);
if($result)
  return '恭喜您!搶到了哦';

不過這里還存在一個問題就是一個用戶搶購多次插佛,我們繼續(xù)優(yōu)化代碼杠巡,將搶購成功的用戶放入set中,并判斷新?lián)屬彽挠脩羰欠褚呀?jīng)在set中雇寇。若存在則返回已經(jīng)搶購成功的提示氢拥。

$user_id =  \Session::get('user_id');//當(dāng)前搶購用戶id
/* 模擬搶購操作,搶購前判斷redis隊列庫存量 */
$count=\Redis::lpop('goods_store:1');
if(!$count)
  return '已經(jīng)搶光了哦!';
$exist_user = \Redis::sIsMember('order:1',$user_id);
if($exist_user)
      return '已經(jīng)搶購成功了哦谢床!';
$result = \Redis::sAdd('order:1',$user_id);
if($result)
  return '恭喜您!搶到了哦';

為了檢測實際效果兄一,我使用jmeter工具模擬100、200识腿、1000個用戶并發(fā)進(jìn)行搶購,經(jīng)過大量的測試造壮,最終搶購成功的用戶始終為10渡讼,沒有出現(xiàn)“超搶/超賣”。

上面只是簡單模擬高并發(fā)下的搶購思路耳璧,真實場景要比這復(fù)雜很多成箫,比如雙11活動遠(yuǎn)遠(yuǎn)比這更復(fù)雜多啦,很多注意的地方如搶購活動頁面做成靜態(tài)的旨枯,通過ajax調(diào)用接口等等蹬昌。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市攀隔,隨后出現(xiàn)的幾起案子皂贩,更是在濱河造成了極大的恐慌,老刑警劉巖昆汹,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件明刷,死亡現(xiàn)場離奇詭異,居然都是意外死亡满粗,警方通過查閱死者的電腦和手機(jī)辈末,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人挤聘,你說我怎么就攤上這事轰枝。” “怎么了组去?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵鞍陨,是天一觀的道長。 經(jīng)常有香客問我添怔,道長湾戳,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任广料,我火速辦了婚禮砾脑,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘艾杏。我一直安慰自己韧衣,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布购桑。 她就那樣靜靜地躺著畅铭,像睡著了一般。 火紅的嫁衣襯著肌膚如雪勃蜘。 梳的紋絲不亂的頭發(fā)上硕噩,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天,我揣著相機(jī)與錄音缭贡,去河邊找鬼炉擅。 笑死,一個胖子當(dāng)著我的面吹牛阳惹,可吹牛的內(nèi)容都是我干的谍失。 我是一名探鬼主播,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼莹汤,長吁一口氣:“原來是場噩夢啊……” “哼快鱼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起纲岭,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤抹竹,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后荒勇,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體柒莉,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年沽翔,在試婚紗的時候發(fā)現(xiàn)自己被綠了兢孝。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片窿凤。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖跨蟹,靈堂內(nèi)的尸體忽然破棺而出雳殊,到底是詐尸還是另有隱情,我是刑警寧澤窗轩,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布夯秃,位于F島的核電站,受9級特大地震影響痢艺,放射性物質(zhì)發(fā)生泄漏仓洼。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一堤舒、第九天 我趴在偏房一處隱蔽的房頂上張望色建。 院中可真熱鬧,春花似錦舌缤、人聲如沸箕戳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽陵吸。三九已至,卻和暖如春介牙,著一層夾襖步出監(jiān)牢的瞬間壮虫,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工环础, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留旨指,地道東北人。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓喳整,卻偏偏與公主長得像,于是被迫代替她去往敵國和親裸扶。 傳聞我的和親對象是個殘疾皇子框都,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,619評論 2 354

推薦閱讀更多精彩內(nèi)容