秒殺場(chǎng)景的緩存、隊(duì)列、鎖使用Redis優(yōu)化設(shè)計(jì)方案
一、為什么難
秒殺系統(tǒng)難做的原因:庫(kù)存只有一份,所有人會(huì)在集中的時(shí)間讀和寫(xiě)這些數(shù)據(jù)。例如小米手機(jī)每周二的秒殺,可能手機(jī)只有1萬(wàn)部,但瞬時(shí)進(jìn)入的流量可能是幾百幾千萬(wàn)。又例如12306搶票,亦與秒殺類(lèi)似,瞬時(shí)流量更甚。這篇文章主要介紹了秒殺場(chǎng)景的緩存、隊(duì)列、鎖使用Redis優(yōu)化設(shè)計(jì)方案,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
主要需要解決的問(wèn)題有兩個(gè):
- 高并發(fā)對(duì)數(shù)據(jù)庫(kù)產(chǎn)生的壓力
- 競(jìng)爭(zhēng)狀態(tài)下如何解決庫(kù)存的正確減少(
超賣(mài)問(wèn)題)
對(duì)于第一個(gè)問(wèn)題,已經(jīng)很容易想到用緩存來(lái)處理?yè)屬?gòu),避免直接操作數(shù)據(jù)庫(kù),例如使用Redis。重點(diǎn)在于第二個(gè)問(wèn)題,常規(guī)寫(xiě)法:
查詢出對(duì)應(yīng)商品的庫(kù)存,看是否大于0,然后執(zhí)行生成訂單等操作,但是在判斷庫(kù)存是否大于0處,如果在高并發(fā)下就會(huì)有問(wèn)題,導(dǎo)致庫(kù)存量出現(xiàn)負(fù)數(shù)
二、常見(jiàn)架構(gòu)

流量到了億級(jí)別,常見(jiàn)站點(diǎn)架構(gòu)如上:
- 瀏覽器端,最上層,會(huì)執(zhí)行到一些JS代碼
- 站點(diǎn)層,這一層會(huì)訪問(wèn)后端數(shù)據(jù),拼html頁(yè)面返回給瀏覽器
- 服務(wù)層,向上游屏蔽底層數(shù)據(jù)細(xì)節(jié)
- 數(shù)據(jù)層,最終的庫(kù)存是存在這里的,mysql是一個(gè)典型
三、優(yōu)化方向
1)將請(qǐng)求盡量攔截在系統(tǒng)上游:傳統(tǒng)秒殺系統(tǒng)之所以掛,請(qǐng)求都?jí)旱沽撕蠖藬?shù)據(jù)層,數(shù)據(jù)讀寫(xiě)鎖沖突嚴(yán)重,并發(fā)高響應(yīng)慢,幾乎所有請(qǐng)求都超時(shí),流量雖大,下單成功的有效流量甚小【一趟火車(chē)其實(shí)只有2000張票,200w個(gè)人來(lái)買(mǎi),基本沒(méi)有人能買(mǎi)成功,請(qǐng)求有效率為0】
2)充分利用緩存:這是一個(gè)典型的讀多寫(xiě)少的應(yīng)用場(chǎng)景【一趟火車(chē)其實(shí)只有2000張票,200w個(gè)人來(lái)買(mǎi),最多2000個(gè)人下單成功,其他人都是查詢庫(kù)存,寫(xiě)比例只有0.1%,讀比例占99.9%】,非常適合使用緩存。
四、優(yōu)化細(xì)節(jié)
4.1)瀏覽器層請(qǐng)求攔截
點(diǎn)擊了“查詢”按鈕之后,系統(tǒng)那個(gè)卡呀,進(jìn)度條漲的慢呀,作為用戶,我會(huì)不自覺(jué)的再去點(diǎn)擊“查詢”,繼續(xù)點(diǎn),繼續(xù)點(diǎn),點(diǎn)點(diǎn)點(diǎn)。。。有用么?平白無(wú)故的增加了系統(tǒng)負(fù)載(一個(gè)用戶點(diǎn)5次,80%的請(qǐng)求是這么多出來(lái)的),怎么整?
a 產(chǎn)品層面,用戶點(diǎn)擊“查詢”或者“購(gòu)票”后,按鈕置灰,禁止用戶重復(fù)提交請(qǐng)求
b JS層面,限制用戶在x秒之內(nèi)只能提交一次請(qǐng)求
如此限流,80%流量已攔。
4.2)站點(diǎn)層請(qǐng)求攔截與頁(yè)面緩存
瀏覽器層的請(qǐng)求攔截,只能攔住小白用戶(不過(guò)這是99%的用戶喲),高端的程序員根本不吃這一套,寫(xiě)個(gè)for循環(huán),直接調(diào)用你后端的http請(qǐng)求,怎么整?
a 同一個(gè)uid,限制訪問(wèn)頻度,做頁(yè)面緩存,x秒內(nèi)到達(dá)站點(diǎn)層的請(qǐng)求,均返回同一頁(yè)面
b 同一個(gè)item的查詢,例如手機(jī)車(chē)次,做頁(yè)面緩存,x秒內(nèi)到達(dá)站點(diǎn)層的請(qǐng)求,均返回同一頁(yè)面
如此限流,又有99%的流量會(huì)被攔截在站點(diǎn)層
4.3)服務(wù)層請(qǐng)求攔截與數(shù)據(jù)緩存站點(diǎn)層的請(qǐng)求攔截,只能攔住普通程序員,高級(jí)黑客,假設(shè)他控制了10w臺(tái)肉雞(并且假設(shè)買(mǎi)票不需要實(shí)名認(rèn)證),這下uid的限制不行了吧?怎么整?
a 大哥,我是服務(wù)層,我清楚的知道小米只有1萬(wàn)部手機(jī),我清楚的知道一列火車(chē)只有2000張車(chē)票,我透10w個(gè)請(qǐng)求去數(shù)據(jù)庫(kù)有什么意義呢?對(duì)于寫(xiě)請(qǐng)求,做請(qǐng)求隊(duì)列,每次只透有限的寫(xiě)請(qǐng)求去數(shù)據(jù)層,如果均成功再放下一批,如果庫(kù)存不夠則隊(duì)列里的寫(xiě)請(qǐng)求全部返回“已售完”
b 對(duì)于讀請(qǐng)求,還要我說(shuō)么?cache抗,不管是memcached還是redis,單機(jī)抗個(gè)每秒10w應(yīng)該都是沒(méi)什么問(wèn)題的
如此限流,只有非常少的寫(xiě)請(qǐng)求,和非常少的讀緩存mis的請(qǐng)求會(huì)透到數(shù)據(jù)層去,又有99.9%的請(qǐng)求被攔住了
4.4)數(shù)據(jù)層到了數(shù)據(jù)這一層,幾乎就沒(méi)有什么請(qǐng)求了,單機(jī)也能扛得住,還是那句話,庫(kù)存是有限的,小米的產(chǎn)能有限,透這么多請(qǐng)求來(lái)數(shù)據(jù)庫(kù)沒(méi)有意義。
4.5)mysql批量入庫(kù)提高INSERT效率
五、Redis
使用redis隊(duì)列(list),push和pop操作保證了原子性的實(shí)現(xiàn)。即使有很多用戶同時(shí)到達(dá),也是依次執(zhí)行。(mysql事務(wù)在高并發(fā)下性能下降很厲害)
先將商品庫(kù)存存入隊(duì)列:
<?php
$store=1000; //商品庫(kù)存
$redis=new Redis();
$result=$redis->connect('127.0.0.1',6379);
$res=$redis->llen('goods_store');
for($i=0; $i<$store; $i++){
$redis->lpush('goods_store',1);
}
echo $redis->llen('goods_store');
?>客戶執(zhí)行下單操作:
$redis=new Redis();
$result=$redis->connect('127.0.0.1',6379);
$count = $redis->lpop('goods_store');
if(!$count){
echo '搶購(gòu)失?。?;
return;
}緩存也是可以應(yīng)對(duì)寫(xiě)請(qǐng)求的,比如我們就可以把數(shù)據(jù)庫(kù)中的庫(kù)存數(shù)據(jù)轉(zhuǎn)移到Redis緩存中,所有減庫(kù)存操作都在Redis中進(jìn)行,然后再通過(guò)后臺(tái)進(jìn)程把Redis中的用戶秒殺請(qǐng)求同步到數(shù)據(jù)庫(kù)中
六、總結(jié)
沒(méi)什么總結(jié)了,上文應(yīng)該描述的非常清楚了,對(duì)于秒殺系統(tǒng),再次重復(fù)下兩個(gè)架構(gòu)優(yōu)化思路:1)盡量將請(qǐng)求攔截在系統(tǒng)上游2)讀多寫(xiě)少經(jīng)量多使用緩存3) redis隊(duì)列緩存 + mysql 批量入庫(kù)
到此這篇關(guān)于秒殺場(chǎng)景的緩存、隊(duì)列、鎖使用Redis優(yōu)化設(shè)計(jì)方案的文章就介紹到這了,更多相關(guān)Redis秒殺優(yōu)化設(shè)計(jì)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解決了Ajax、MySQL 和 Zend Framework 的亂碼問(wèn)題
功夫不負(fù)有心人,花了將近一天時(shí)間,終于解決了Ajax 、MySQL 和 Zend Framework 的亂碼問(wèn)題?,F(xiàn)在總結(jié)如下,以供參考。2009-03-03
談?wù)凱HP連接Access數(shù)據(jù)庫(kù)的注意事項(xiàng)
有的時(shí)候需要用php連接access數(shù)據(jù)庫(kù),結(jié)果整了半天Access數(shù)據(jù)庫(kù)就是連接不上,查找很多資料,以下是些個(gè)人經(jīng)驗(yàn),希望能給需要連接access 數(shù)據(jù)的人帶來(lái)幫助。2016-08-08
PHP獲取redis里不存在的6位隨機(jī)數(shù)應(yīng)用示例【設(shè)置24小時(shí)過(guò)時(shí)】
這篇文章主要介紹了PHP獲取redis里不存在的6位隨機(jī)數(shù)的方法,可設(shè)置24小時(shí)過(guò)時(shí)限制,涉及php字符串及數(shù)據(jù)庫(kù)相關(guān)操作技巧,需要的朋友可以參考下2017-06-06
php使用GuzzleHttp實(shí)現(xiàn)HTTP請(qǐng)求
這篇文章主要為大家詳細(xì)介紹了php如何使用GuzzleHttp實(shí)現(xiàn)HTTP請(qǐng)求,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-11-11
PHP 冒泡排序 二分查找 順序查找 二維數(shù)組排序算法函數(shù)的詳解
本篇文章是對(duì)PHP 冒泡排序 二分查找 順序查找 二維數(shù)組排序算法函數(shù)進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-06-06
PHP+MYSQL會(huì)員系統(tǒng)的開(kāi)發(fā)實(shí)例教程
這篇文章主要介紹了PHP+MYSQL會(huì)員系統(tǒng)的開(kāi)發(fā)實(shí)例教程,通過(guò)一個(gè)完整的會(huì)員系統(tǒng)開(kāi)發(fā),進(jìn)一步加深對(duì)PHP+MySQL程序設(shè)計(jì)流程的認(rèn)識(shí),需要的朋友可以參考下2014-08-08
php寫(xiě)的帶緩存數(shù)據(jù)功能的mysqli類(lèi)
本文分享一個(gè)帶緩存數(shù)據(jù)功能的mysqli類(lèi),非常好用2012-09-09

