秒殺系統(tǒng)架構(gòu)優(yōu)化思路

文章來自于:秒殺系統(tǒng)架構(gòu)優(yōu)化思路

秒殺業(yè)務(wù)為什么難做

  • im系統(tǒng)矛渴,例如qq或者微博,每個人都讀自己的數(shù)據(jù)(好友列表颅停、群列表谓晌、個人信息);
  • 微博系統(tǒng)癞揉,每個人讀你關(guān)注的人的數(shù)據(jù)纸肉,一個人讀多個人的數(shù)據(jù);
  • 秒殺系統(tǒng)喊熟,庫存只有一份柏肪,所有人會在集中的時間讀和寫這些數(shù)據(jù),多個人讀一個數(shù)據(jù)芥牌。

例如:小米手機每周二的秒殺烦味,可能手機只有1萬部,但瞬時進入的流量可能是幾百幾千萬壁拉。

又例如:12306搶票谬俄,票是有限的柏靶,庫存一份,瞬時流量非常多凤瘦,都讀相同的庫存宿礁。讀寫沖突,鎖非常嚴重蔬芥,這是秒殺業(yè)務(wù)難的地方梆靖。那我們怎么優(yōu)化秒殺業(yè)務(wù)的架構(gòu)呢?

優(yōu)化方向

優(yōu)化方向有兩個(今天就講這兩個點):

  • 將請求盡量攔截在系統(tǒng)上游(不要讓鎖沖突落到數(shù)據(jù)庫上去)笔诵。傳統(tǒng)秒殺系統(tǒng)之所以掛返吻,請求都壓倒了后端數(shù)據(jù)層,數(shù)據(jù)讀寫鎖沖突嚴重乎婿,并發(fā)高響應慢测僵,幾乎所有請求都超時,流量雖大谢翎,下單成功的有效流量甚小捍靠。以12306為例,一趟火車其實只有2000張票森逮,200w個人來買榨婆,基本沒有人能買成功,請求有效率為0褒侧。
  • 充分利用緩存良风,秒殺買票,這是一個典型的讀多些少的應用場景闷供,大部分請求是車次查詢烟央,票查詢,下單和支付才是寫請求歪脏。一趟火車其實只有2000張票疑俭,200w個人來買,最多2000個人下單成功婿失,其他人都是查詢庫存怠硼,寫比例只有0.1%,讀比例占99.9%移怯,非常適合使用緩存來優(yōu)化香璃。好,后續(xù)講講怎么個“將請求盡量攔截在系統(tǒng)上游”法舟误,以及怎么個“緩存”法葡秒,講講細節(jié)。

常見秒殺架構(gòu)

常見的站點架構(gòu)基本是這樣的(絕對不畫忽悠類的架構(gòu)圖)


  • 瀏覽器端,最上層眯牧,會執(zhí)行到一些JS代碼
  • 站點層蹋岩,這一層會訪問后端數(shù)據(jù),拼html頁面返回給瀏覽器
  • 服務(wù)層学少,向上游屏蔽底層數(shù)據(jù)細節(jié)剪个,提供數(shù)據(jù)訪問
  • 數(shù)據(jù)層,最終的庫存是存在這里的版确,mysql是一個典型(當然還有會緩存)

這個圖雖然簡單扣囊,但能形象的說明大流量高并發(fā)的秒殺業(yè)務(wù)架構(gòu),大家要記得這一張圖绒疗。
后面細細解析各個層級怎么優(yōu)化侵歇。

各層次優(yōu)化細節(jié)

第一層,客戶端怎么優(yōu)化(瀏覽器層吓蘑,APP層)

問大家一個問題惕虑,大家都玩過微信的搖一搖搶紅包對吧,每次搖一搖磨镶,就會往后端發(fā)送請求么溃蔫?回顧我們下單搶票的場景,點擊了“查詢”按鈕之后琳猫,系統(tǒng)那個卡呀酒唉,進度條漲的慢呀,作為用戶沸移,我會不自覺的再去點擊“查詢”,對么侄榴?繼續(xù)點雹锣,繼續(xù)點,點點點癞蚕。蕊爵。。有用么桦山?平白無故的增加了系統(tǒng)負載攒射,一個用戶點5次,80%的請求是這么多出來的恒水,怎么整会放?

  • 產(chǎn)品層面,用戶點擊“查詢”或者“購票”后钉凌,按鈕置灰咧最,禁止用戶重復提交請求;
    -JS層面,限制用戶在x秒之內(nèi)只能提交一次請求矢沿;

APP層面滥搭,可以做類似的事情,雖然你瘋狂的在搖微信捣鲸,其實x秒才向后端發(fā)起一次請求瑟匆。這就是所謂的“將請求盡量攔截在系統(tǒng)上游”,越上游越好栽惶,瀏覽器層愁溜,APP層就給攔住,這樣就能擋住80%+的請求媒役,這種辦法只能攔住普通用戶(但99%的用戶是普通用戶)對于群內(nèi)的高端程序員是攔不住的祝谚。firebug一抓包,http長啥樣都知道酣衷,js是萬萬攔不住程序員寫for循環(huán)交惯,調(diào)用http接口的,這部分請求怎么處理穿仪?

第二層席爽,站點層面的請求攔截

怎么攔截?怎么防止程序員寫for循環(huán)調(diào)用啊片,有去重依據(jù)么只锻?ip?cookie-id紫谷?…想復雜了齐饮,這類業(yè)務(wù)都需要登錄,用uid即可笤昨。在站點層面祖驱,對uid進行請求計數(shù)和去重,甚至不需要統(tǒng)一存儲計數(shù)瞒窒,直接站點層內(nèi)存存儲(這樣計數(shù)會不準捺僻,但最簡單)。一個uid崇裁,5秒只準透過1個請求匕坯,這樣又能攔住99%的for循環(huán)請求。
5s只透過一個請求拔稳,其余的請求怎么辦葛峻?緩存,頁面緩存巴比,同一個uid泞歉,限制訪問頻度逼侦,做頁面緩存,x秒內(nèi)到達站點層的請求腰耙,均返回同一頁面榛丢。同一個item的查詢,例如車次挺庞,做頁面緩存晰赞,x秒內(nèi)到達站點層的請求,均返回同一頁面选侨。如此限流掖鱼,既能保證用戶有良好的用戶體驗(沒有返回404)又能保證系統(tǒng)的健壯性(利用頁面緩存,把請求攔截在站點層了)援制。
頁面緩存不一定要保證所有站點返回一致的頁面戏挡,直接放在每個站點的內(nèi)存也是可以的。優(yōu)點是簡單晨仑,壞處是http請求落到不同的站點褐墅,返回的車票數(shù)據(jù)可能不一樣,這是站點層的請求攔截與緩存優(yōu)化洪己。

好妥凳,這個方式攔住了寫for循環(huán)發(fā)http請求的程序員,有些高端程序員(黑客)控制了10w個肉雞答捕,手里有10w個uid逝钥,同時發(fā)請求(先不考慮實名制的問題拱镐,小米搶手機不需要實名制)艘款,這下怎么辦,站點層按照uid限流攔不住了沃琅。

第三層 服務(wù)層來攔截(反正就是不要讓請求落到數(shù)據(jù)庫上去)

服務(wù)層怎么攔截哗咆?大哥,我是服務(wù)層阵难,我清楚的知道小米只有1萬部手機,我清楚的知道一列火車只有2000張車票芒填,我透10w個請求去數(shù)據(jù)庫有什么意義呢呜叫?沒錯,請求隊列殿衰!
對于寫請求朱庆,做請求隊列,每次只透有限的寫請求去數(shù)據(jù)層(下訂單闷祥,支付這樣的寫業(yè)務(wù))
1w部手機娱颊,只透1w個下單請求去db
3k張火車票傲诵,只透3k個下單請求去db
如果均成功再放下一批,如果庫存不夠則隊列里的寫請求全部返回“已售完”箱硕。

對于讀請求拴竹,怎么優(yōu)化?cache抗剧罩,不管是memcached還是redis栓拜,單機抗個每秒10w應該都是沒什么問題的。如此限流惠昔,只有非常少的寫請求幕与,和非常少的讀緩存mis的請求會透到數(shù)據(jù)層去,又有99.9%的請求被攔住了镇防。

當然啦鸣,還有業(yè)務(wù)規(guī)則上的一些優(yōu)化±囱酰回想12306所做的诫给,分時分段售票,原來統(tǒng)一10點賣票饲漾,現(xiàn)在8點蝙搔,8點半,9點考传,...每隔半個小時放出一批:將流量攤勻吃型。
其次,數(shù)據(jù)粒度的優(yōu)化:你去購票僚楞,對于余票查詢這個業(yè)務(wù)勤晚,票剩了58張,還是26張泉褐,你真的關(guān)注么赐写,其實我們只關(guān)心有票和無票?流量大的時候膜赃,做一個粗粒度的“有票”“無票”緩存即可挺邀。

一些業(yè)務(wù)邏輯的異步:例如下單業(yè)務(wù)與 支付業(yè)務(wù)的分離。這些優(yōu)化都是結(jié)合 業(yè)務(wù) 來的跳座,我之前分享過一個觀點“一切脫離業(yè)務(wù)的架構(gòu)設(shè)計都是耍流氓”架構(gòu)的優(yōu)化也要針對業(yè)務(wù)端铛。

第四層 數(shù)據(jù)庫層

瀏覽器攔截了80%,站點層攔截了99.9%并做了頁面緩存疲眷,服務(wù)層又做了寫請求隊列與數(shù)據(jù)緩存禾蚕,每次透到數(shù)據(jù)庫層的請求都是可控的。db基本就沒什么壓力了狂丝,閑庭信步换淆,單機也能扛得住哗总,還是那句話,庫存是有限的倍试,小米的產(chǎn)能有限讯屈,透這么多請求來數(shù)據(jù)庫沒有意義。
全部透到數(shù)據(jù)庫易猫,100w個下單耻煤,0個成功,請求有效率0%准颓。透3k個到數(shù)據(jù)哈蝇,全部成功,請求有效率100%

總結(jié)

上文應該描述的非常清楚了攘已,沒什么總結(jié)了炮赦,對于秒殺系統(tǒng),再次重復下我個人經(jīng)驗的兩個架構(gòu)優(yōu)化思路:

  • 盡量將請求攔截在系統(tǒng)上游(越上游越好)样勃;
  • 讀多寫少的常用多使用緩存(緩存抗讀壓力)吠勘;

瀏覽器和APP:做限速
站點層:按照uid做限速,做頁面緩存
服務(wù)層:按照業(yè)務(wù)做寫請求隊列控制流量峡眶,做數(shù)據(jù)緩存
數(shù)據(jù)層:閑庭信步
并且:結(jié)合業(yè)務(wù)做優(yōu)化

Q&A

問題1

按你的架構(gòu)剧防,其實壓力最大的反而是站點層,假設(shè)真實有效的請求數(shù)有1000萬辫樱,不太可能限制請求連接數(shù)吧峭拘,那么這部分的壓力怎么處理?
答:每秒鐘的并發(fā)可能沒有1kw狮暑,假設(shè)有1kw鸡挠,解決方案2個:

  • 站點層是可以通過加機器擴容的,最不濟1k臺機器來唄搬男。
  • 如果機器不夠拣展,拋棄請求,拋棄50%(50%直接返回稍后再試)缔逛,原則是要保護系統(tǒng)备埃,不能讓所有用戶都失敗。

問題2

“控制了10w個肉雞褐奴,手里有10w個uid按脚,同時發(fā)請求” 這個問題怎么解決哈?
答: 上面說了歉糜,服務(wù)層寫請求隊列控制

問題3

限制訪問頻次的緩存乘寒,是否也可以用于搜索望众?例如A用戶搜索了“手機”匪补,B用戶搜索“手機”伞辛,優(yōu)先使用A搜索后生成的緩存頁面?
答:這個是可以的夯缺,這個方法也經(jīng)常用在“動態(tài)”運營活動頁蚤氏,例如短時間推送4kw用戶app-push運營活動,做頁面緩存踊兜。

問題4:

如果隊列處理失敗竿滨,如何處理?肉雞把隊列被撐爆了怎么辦捏境?
答:處理失敗返回下單失敗于游,讓用戶再試。隊列成本很低垫言,爆了很難吧贰剥。最壞的情況下,緩存了若干請求之后筷频,后續(xù)請求都直接返回“無票”(隊列里已經(jīng)有100w請求了蚌成,都等著,再接受請求也沒有意義了)

問題5:

站點層過濾的話凛捏,是把uid請求數(shù)單獨保存到各個站點的內(nèi)存中么担忧?如果是這樣的話,怎么處理多臺服務(wù)器集群經(jīng)過負載均衡器將相同用戶的響應分布到不同服務(wù)器的情況呢坯癣?還是說將站點層的過濾放到負載均衡前瓶盛?
答:可以放在內(nèi)存,這樣的話看似一臺服務(wù)器限制了5s一個請求坡锡,全局來說(假設(shè)有10臺機器)蓬网,其實是限制了5s 10個請求,解決辦法:

  • 加大限制(這是建議的方案鹉勒,最簡單)
  • 在nginx層做7層均衡帆锋,讓一個uid的請求盡量落到同一個機器上

問題6:

服務(wù)層過濾的話,隊列是服務(wù)層統(tǒng)一的一個隊列禽额?還是每個提供服務(wù)的服務(wù)器各一個隊列锯厢?如果是統(tǒng)一的一個隊列的話,需不需要在各個服務(wù)器提交的請求入隊列前進行鎖控制脯倒?
答:可以不用統(tǒng)一一個隊列实辑,這樣的話每個服務(wù)透過更少量的請求(總票數(shù)/服務(wù)個數(shù)),這樣簡單藻丢。統(tǒng)一一個隊列又復雜了剪撬。

問題7:

秒殺之后的支付完成,以及未支付取消占位悠反,如何對剩余庫存做及時的控制更新残黑?
答:數(shù)據(jù)庫里一個狀態(tài)馍佑,未支付。如果超過時間梨水,例如45分鐘拭荤,庫存會重新會恢復(大家熟知的“回倉”),給我們搶票的啟示是疫诽,開動秒殺后舅世,45分鐘之后再試試看,說不定又有票喲~

問題8:

不同的用戶瀏覽同一個商品 落在不同的緩存實例顯示的庫存完全不一樣 請問老師怎么做緩存數(shù)據(jù)一致或者是允許臟讀奇徒?
答:目前的架構(gòu)設(shè)計雏亚,請求落到不同的站點上,數(shù)據(jù)可能不一致(頁面緩存不一樣)摩钙,這個業(yè)務(wù)場景能接受评凝。但數(shù)據(jù)庫層面真實數(shù)據(jù)是沒問題的。

問題9:

就算處于業(yè)務(wù)把優(yōu)化考慮“3k張火車票腺律,只透3k個下單請求去db”那這3K個訂單就不會發(fā)生擁堵了嗎奕短?
答:(1)數(shù)據(jù)庫抗3k個寫請求還是ok的;(2)可以數(shù)據(jù)拆分匀钧;(3)如果3k扛不住翎碑,服務(wù)層可以控制透過去的并發(fā)數(shù)量,根據(jù)壓測情況來吧之斯,3k只是舉例日杈;

問題10:

如果在站點層或者服務(wù)層處理后臺失敗的話,需不需要考慮對這批處理失敗的請求做重放佑刷?還是就直接丟棄莉擒?
答:別重放了,返回用戶查詢失敗或者下單失敗吧瘫絮,架構(gòu)設(shè)計原則之一是“fail fast”涨冀。

問題11:

對于大型系統(tǒng)的秒殺,比如12306麦萤,同時進行的秒殺活動很多鹿鳖,如何分流?
答:垂直拆分

問題12

額外又想到一個問題壮莹。這套流程做成同步還是異步的翅帜?如果是同步的話,應該還存在會有響應反饋慢的情況命满。但如果是異步的話涝滴,如何控制能夠?qū)㈨憫Y(jié)果返回正確的請求方?
答:用戶層面肯定是同步的(用戶的http請求是夯住的),服務(wù)層面可以同步可以異步歼疮。

問題13:

秒殺群提問:減庫存是在那個階段減呢僵娃?如果是下單鎖庫存的話,大量惡意用戶下單鎖庫存而不支付如何處理呢腋妙?
答:數(shù)據(jù)庫層面寫請求量很低,還好讯榕,下單不支付骤素,等時間過完再“回倉”,之前提過了愚屁。

文章來自于:秒殺系統(tǒng)架構(gòu)優(yōu)化思路

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末济竹,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子霎槐,更是在濱河造成了極大的恐慌送浊,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,589評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件丘跌,死亡現(xiàn)場離奇詭異袭景,居然都是意外死亡,警方通過查閱死者的電腦和手機闭树,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評論 3 396
  • 文/潘曉璐 我一進店門耸棒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人报辱,你說我怎么就攤上這事与殃。” “怎么了碍现?”我有些...
    開封第一講書人閱讀 165,933評論 0 356
  • 文/不壞的土叔 我叫張陵幅疼,是天一觀的道長。 經(jīng)常有香客問我昼接,道長爽篷,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,976評論 1 295
  • 正文 為了忘掉前任慢睡,我火速辦了婚禮狼忱,結(jié)果婚禮上假颇,老公的妹妹穿的比我還像新娘晓折。我一直安慰自己,他們只是感情好薄榛,可當我...
    茶點故事閱讀 67,999評論 6 393
  • 文/花漫 我一把揭開白布者吁。 她就那樣靜靜地躺著窘俺,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上瘤泪,一...
    開封第一講書人閱讀 51,775評論 1 307
  • 那天灶泵,我揣著相機與錄音,去河邊找鬼对途。 笑死赦邻,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的实檀。 我是一名探鬼主播惶洲,決...
    沈念sama閱讀 40,474評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼膳犹!你這毒婦竟也來了恬吕?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,359評論 0 276
  • 序言:老撾萬榮一對情侶失蹤须床,失蹤者是張志新(化名)和其女友劉穎铐料,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體豺旬,經(jīng)...
    沈念sama閱讀 45,854評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡钠惩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,007評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了族阅。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片妻柒。...
    茶點故事閱讀 40,146評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖耘分,靈堂內(nèi)的尸體忽然破棺而出举塔,到底是詐尸還是另有隱情,我是刑警寧澤求泰,帶...
    沈念sama閱讀 35,826評論 5 346
  • 正文 年R本政府宣布央渣,位于F島的核電站,受9級特大地震影響渴频,放射性物質(zhì)發(fā)生泄漏芽丹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,484評論 3 331
  • 文/蒙蒙 一卜朗、第九天 我趴在偏房一處隱蔽的房頂上張望拔第。 院中可真熱鬧,春花似錦场钉、人聲如沸蚊俺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,029評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽泳猬。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間得封,已是汗流浹背埋心。 一陣腳步聲響...
    開封第一講書人閱讀 33,153評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留忙上,地道東北人拷呆。 一個月前我還...
    沈念sama閱讀 48,420評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像疫粥,于是被迫代替她去往敵國和親茬斧。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,107評論 2 356

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