高并发设计方案二(秒杀架构)
來源:https://blog.csdn.net/u013372487/article/details/53436822
優(yōu)化方向:
(1)將請求盡量攔截在系統(tǒng)上游(不要讓鎖沖突落到數(shù)據(jù)庫上去)。傳統(tǒng)秒殺系統(tǒng)之所以掛,請求都壓倒了后端數(shù)據(jù)層,數(shù)據(jù)讀寫鎖沖突嚴(yán)重,并發(fā)高響應(yīng)慢,幾乎所有請求都超時,流量雖大,下單成功的有效流量甚小。以12306為例,一趟火車其實只有2000張票,200w個人來買,基本沒有人能買成功,請求有效率為0。
(2)充分利用緩存,秒殺買票,這是一個典型的讀多寫少的應(yīng)用場景,大部分請求是車次查詢,票查詢,下單和支付才是寫請求。一趟火車其實只有2000張票,200w個人來買,最多2000個人下單成功,其他人都是查詢庫存,寫比例只有0.1%,讀比例占99.9%,非常適合使用緩存來優(yōu)化。
常見秒殺架構(gòu):
?
(1)瀏覽器端,最上層,會執(zhí)行到一些JS代碼?
(2)站點層,這一層會訪問后端數(shù)據(jù),拼html頁面返回給瀏覽器?
(3)服務(wù)層,向上游屏蔽底層數(shù)據(jù)細(xì)節(jié),提供數(shù)據(jù)訪問?
(4)數(shù)據(jù)層,最終的庫存是存在這里的,mysql是一個典型(當(dāng)然還有會緩存)
各層次優(yōu)化細(xì)節(jié):
第一層,客戶端怎么優(yōu)化(瀏覽器層,APP層)
問大家一個問題,大家都玩過微信的搖一搖搶紅包對吧,每次搖一搖,就會往后端發(fā)送請求么?回顧我們下單搶票的場景,點擊了“查詢”按鈕之后,系統(tǒng)那個卡呀,進度條漲的慢呀,作為用戶,我會不自覺的再去點擊“查詢”,對么?繼續(xù)點,繼續(xù)點,點點點。。。有用么?平白無故的增加了系統(tǒng)負(fù)載,一個用戶點5次,80%的請求是這么多出來的,怎么整?
<span style="color:#000000"><code>(a)產(chǎn)品層面,用戶點擊“查詢”或者“購票”后,按鈕置灰,禁止用戶重復(fù)提交請求; (b)JS層面,限制用戶在x秒之內(nèi)只能提交一次請求; (c)APP層面,可以做類似的事情,雖然你瘋狂的在搖微信,其實x秒才向后端發(fā)起一次請求。</code></span>這就是所謂的“將請求盡量攔截在系統(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?…想復(fù)雜了,這類業(yè)務(wù)都需要登錄,用uid即可。在站點層面,對uid進行請求計數(shù)和去重,甚至不需要統(tǒng)一存儲計數(shù),直接站點層內(nèi)存存儲(這樣計數(shù)會不準(zhǔn),但最簡單)。一個uid,5秒只準(zhǔn)透過1個請求,這樣又能攔住99%的for循環(huán)請求。?
5s只透過一個請求,其余的請求怎么辦?緩存,頁面緩存,同一個uid,限制訪問頻度,做頁面緩存,x秒內(nèi)到達(dá)站點層的請求,均返回同一頁面。同一個item的查詢,例如車次,做頁面緩存,x秒內(nèi)到達(dá)站點層的請求,均返回同一頁面。如此限流,既能保證用戶有良好的用戶體驗(沒有返回404)又能保證系統(tǒng)的健壯性(利用頁面緩存,把請求攔截在站點層了)。?
頁面緩存不一定要保證所有站點返回一致的頁面,直接放在每個站點的內(nèi)存也是可以的。優(yōu)點是簡單,壞處是http請求落到不同的站點,返回的車票數(shù)據(jù)可能不一樣,這是站點層的請求攔截與緩存優(yōu)化。
第三層 服務(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應(yīng)該都是沒什么問題的。如此限流,只有非常少的寫請求,和非常少的讀緩存mis的請求會透到數(shù)據(jù)層去,又有99.9%的請求被攔住了。
當(dāng)然,還有業(yè)務(wù)規(guī)則上的一些優(yōu)化?;叵?2306所做的,分時分段售票,原來統(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ù)。
問題說明:
<span style="color:#000000"><code>問題<span style="color:#006666 !important">1</span>、按你的架構(gòu),其實壓力最大的反而是站點層,假設(shè)真實有效的請求數(shù)有<span style="color:#006666 !important">1000</span>萬,不太可能限制請求連接數(shù)吧,那么這部分的壓力怎么處理? 答:每秒鐘的并發(fā)可能沒有<span style="color:#006666 !important">1</span>kw,假設(shè)有<span style="color:#006666 !important">1</span>kw,解決方案<span style="color:#006666 !important">2</span>個: (<span style="color:#006666 !important">1</span>)站點層是可以通過加機器擴容的,最不濟<span style="color:#006666 !important">1</span>k臺機器來唄。 (<span style="color:#006666 !important">2</span>)如果機器不夠,拋棄請求,拋棄<span style="color:#006666 !important">50</span><span style="color:#4f4f4f !important">%(</span><span style="color:#006666 !important">50</span><span style="color:#4f4f4f !important">%直</span>接返回稍后再試),原則是要保護系統(tǒng),不能讓所有用戶都失敗。</code></span> <span style="color:#000000"><code>問題2、“控制了10w個肉雞,手里有10w個uid,同時發(fā)請求” 這個問題怎么解決哈? 答:上面說了,服務(wù)層寫請求隊列控制</code></span> <span style="color:#000000"><code>問題<span style="color:#006666 !important">3</span>:限制訪問頻次的緩存,是否也可以用于搜索?例如<span style="color:#006666 !important">A</span>用戶搜索了“手機”,B用戶搜索“手機”,優(yōu)先使用<span style="color:#006666 !important">A</span>搜索后生成的緩存頁面? 答:這個是可以的,這個方法也經(jīng)常用在“動態(tài)”運營活動頁,例如短時間推送<span style="color:#006666 !important">4</span>kw用戶app-push運營活動,做頁面緩存。</code></span> <span style="color:#000000"><code>問題4:如果隊列處理失敗,如何處理?肉雞把隊列被撐爆了怎么辦? 答:處理失敗返回下單失敗,讓用戶再試。隊列成本很低,爆了很難吧。最壞的情況下,緩存了若干請求之后,后續(xù)請求都直接返回“無票”(隊列里已經(jīng)有100w請求了,都等著,再接受請求也沒有意義了)</code></span> <span style="color:#000000"><code>問題5:站點層過濾的話,是把uid請求數(shù)單獨保存到各個站點的內(nèi)存中么?如果是這樣的話,怎么處理多臺服務(wù)器集群經(jīng)過負(fù)載均衡器將相同用戶的響應(yīng)分布到不同服務(wù)器的情況呢?還是說將站點層的過濾放到負(fù)載均衡前? 答:可以放在內(nèi)存,這樣的話看似一臺服務(wù)器限制了5s一個請求,全局來說(假設(shè)有10臺機器),其實是限制了5s 10個請求,解決辦法: 1)加大限制(這是建議的方案,最簡單) 2)在nginx層做7層均衡,讓一個uid的請求盡量落到同一個機器上</code></span> <span style="color:#000000"><code>問題6:服務(wù)層過濾的話,隊列是服務(wù)層統(tǒng)一的一個隊列?還是每個提供服務(wù)的服務(wù)器各一個隊列?如果是統(tǒng)一的一個隊列的話,需不需要在各個服務(wù)器提交的請求入隊列前進行鎖控制? 答:可以不用統(tǒng)一一個隊列,這樣的話每個服務(wù)透過更少量的請求(總票數(shù)/服務(wù)個數(shù)),這樣簡單。統(tǒng)一一個隊列又復(fù)雜了。</code></span> <span style="color:#000000"><code>問題7:秒殺之后的支付完成,以及未支付取消占位,如何對剩余庫存做及時的控制更新? 答:數(shù)據(jù)庫里一個狀態(tài),未支付。如果超過時間,例如45分鐘,庫存會重新會恢復(fù)(大家熟知的“回倉”),給我們搶票的啟示是,開動秒殺后,45分鐘之后再試試看,說不定又有票喲~ </code></span> <span style="color:#000000"><code>問題8:不同的用戶瀏覽同一個商品 落在不同的緩存實例顯示的庫存完全不一樣 請問老師怎么做緩存數(shù)據(jù)一致或者是允許臟讀? 答:目前的架構(gòu)設(shè)計,請求落到不同的站點上,數(shù)據(jù)可能不一致(頁面緩存不一樣),這個業(yè)務(wù)場景能接受。但數(shù)據(jù)庫層面真實數(shù)據(jù)是沒問題的。</code></span> <span style="color:#000000"><code>問題<span style="color:#006666 !important">9</span>:就算處于業(yè)務(wù)把優(yōu)化考慮“<span style="color:#006666 !important">3</span>k張火車票,只透<span style="color:#006666 !important">3</span>k個下單請求去db”那這<span style="color:#006666 !important">3</span><span style="color:#000088 !important">K</span>個訂單就不會發(fā)生擁堵了嗎? 答:(<span style="color:#006666 !important">1</span>)數(shù)據(jù)庫抗<span style="color:#006666 !important">3</span>k個寫請求還是ok的;(<span style="color:#006666 !important">2</span>)可以數(shù)據(jù)拆分;(<span style="color:#006666 !important">3</span>)如果<span style="color:#006666 !important">3</span>k扛不住,服務(wù)層可以控制透過去的并發(fā)數(shù)量,根據(jù)壓測情況來吧,<span style="color:#006666 !important">3</span>k只是舉例;</code></span> <span style="color:#000000"><code>問題<span style="color:#006666 !important">10</span>;如果在站點層或者服務(wù)層處理后臺失敗的話,需不需要考慮對這批處理失敗的請求做重放?還是就直接丟棄? 答:別重放了,返回用戶查詢失敗或者下單失敗吧,架構(gòu)設(shè)計原則之一是“<span style="color:#000088 !important">fail</span> fast”。</code></span> <span style="color:#000000"><code>問題11.對于大型系統(tǒng)的秒殺,比如12306,同時進行的秒殺活動很多,如何分流? 答:垂直拆分</code></span> <span style="color:#000000"><code>問題<span style="color:#006666 !important">12</span>、額外又想到一個問題。這套流程做成同步還是異步的?如果是同步的話,應(yīng)該還存在會有響應(yīng)反饋慢的情況。但如果是異步的話,如何控制能夠?qū)㈨憫?yīng)結(jié)果返回正確的請求方? 答:用戶層面肯定是同步的(用戶的<span style="color:#000088 !important">http</span>請求是夯住的),服務(wù)層面可以同步可以異步。</code></span> <span style="color:#000000"><code>問題13、秒殺群提問:減庫存是在那個階段減呢?如果是下單鎖庫存的話,大量惡意用戶下單鎖庫存而不支付如何處理呢? 答:數(shù)據(jù)庫層面寫請求量很低,還好,下單不支付,等時間過完再“回倉”,之前提過了。</code></span> <span style="color:#000000"><code>用戶提交訂單后減庫存,加入隊列,若加入成功跳轉(zhuǎn)到支付,支付成功移出隊列,記入數(shù)據(jù)庫 (隊列可記錄庫存數(shù) 并且 隊列限制插入訂單數(shù),若插入數(shù)大于庫存數(shù)插入失敗,插入太多也沒有意義) (插入失敗原因分兩塊 沒庫存提示已售罄 有庫存但隊列已滿提示業(yè)務(wù)繁忙請稍后再試)加入購物車不減庫存,只做記錄已加入隊列但在規(guī)定時間內(nèi)未支付訂單,在超時后移出隊列,恢復(fù)庫存,記入數(shù)據(jù)庫(未支付狀態(tài))</code></span>總結(jié)
以上是生活随笔為你收集整理的高并发设计方案二(秒杀架构)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 天下歌词(说一说天下歌词的简介)
- 下一篇: 垭口(说一说垭口的简介)