1、抽獎(jiǎng)系統(tǒng)的背景引入
本文給大家分享一個(gè)之前經(jīng)歷過(guò)的抽獎(jiǎng)系統(tǒng)的流量削峰架構(gòu)的設(shè)計(jì)方案。
抽獎(jiǎng)、搶紅包、秒殺,這類(lèi)系統(tǒng)其實(shí)都有一些共同的特點(diǎn),那就是在某個(gè)時(shí)間點(diǎn)會(huì)瞬間涌入大量的人來(lái)點(diǎn)擊系統(tǒng),給系統(tǒng)造成瞬間高于平時(shí)百倍、千倍甚至幾十萬(wàn)倍的流量壓力。
比如抽獎(jiǎng),有一種場(chǎng)景:某個(gè)網(wǎng)站或者App規(guī)定好了在某個(gè)時(shí)間點(diǎn),所有人都可以參與抽獎(jiǎng),那么可能百萬(wàn)級(jí)的用戶(hù)會(huì)蹲守在那個(gè)時(shí)間點(diǎn),到時(shí)間大家一起參與這個(gè)抽獎(jiǎng)。
搶紅包,可能是某個(gè)電視節(jié)目上,突然說(shuō)掃碼可以搶紅包,那么電視機(jī)前可能千萬(wàn)級(jí)的用戶(hù)會(huì)瞬間一起打開(kāi)手機(jī)掃碼搶紅包。
秒殺更是如此,所謂秒殺,意思是讓大家都在電腦前等著,在某個(gè)時(shí)間突然就可以搶購(gòu)某個(gè)限量的商品
比如某個(gè)手機(jī)平時(shí)賣(mài)5999,現(xiàn)在限量100臺(tái)價(jià)格才2999,50%的折扣,可能百萬(wàn)級(jí)的用戶(hù)就會(huì)蹲守在電腦前在比如凌晨12點(diǎn)一起點(diǎn)擊按鈕搶購(gòu)這款手機(jī)。
類(lèi)似的場(chǎng)景其實(shí)現(xiàn)在是很多的,那么本文就用一個(gè)抽獎(jiǎng)系統(tǒng)舉例,說(shuō)說(shuō)應(yīng)對(duì)這種瞬時(shí)超高并發(fā)的流量,應(yīng)該如何設(shè)計(jì)流量削峰的架構(gòu)來(lái)應(yīng)對(duì),才能保證系統(tǒng)不會(huì)突然跨掉?
2、結(jié)合具體業(yè)務(wù)需求分析抽獎(jiǎng)系統(tǒng)
假設(shè)現(xiàn)在有一個(gè)抽獎(jiǎng)的業(yè)務(wù)場(chǎng)景,用戶(hù)在某個(gè)時(shí)間可以參與抽獎(jiǎng),比如一共有1萬(wàn)個(gè)獎(jiǎng),獎(jiǎng)品就是某個(gè)禮物。
然后參與抽獎(jiǎng)的用戶(hù)可能有幾十萬(wàn),一瞬間可能幾十萬(wàn)請(qǐng)求涌入過(guò)來(lái),接著瞬間其中1萬(wàn)人中獎(jiǎng)了,剩余的人都是沒(méi)中獎(jiǎng)的。然后中獎(jiǎng)的1萬(wàn)人的請(qǐng)求會(huì)聯(lián)動(dòng)調(diào)用禮品服務(wù),完成這1萬(wàn)中獎(jiǎng)人的禮品發(fā)放。
簡(jiǎn)單來(lái)說(shuō),需求場(chǎng)景就是如此,然而這里就有很多的地方值得優(yōu)化了。
3、一個(gè)未經(jīng)過(guò)優(yōu)化的系統(tǒng)架構(gòu)
先來(lái)看一個(gè)未經(jīng)過(guò)任何優(yōu)化的系統(tǒng)架構(gòu),簡(jiǎn)單來(lái)說(shuō)就是有一個(gè)負(fù)載均衡的設(shè)備會(huì)把瞬間涌入的超高并發(fā)的流量轉(zhuǎn)發(fā)到后臺(tái)的抽獎(jiǎng)服務(wù)上。
這個(gè)抽獎(jiǎng)服務(wù)就是用普通的Tomcat來(lái)部署的,里面實(shí)現(xiàn)了具體的抽獎(jiǎng)邏輯,假設(shè)剛開(kāi)始最常規(guī)的抽獎(jiǎng)邏輯是基于MySQL來(lái)實(shí)現(xiàn)的,接著就是基于Tomcat部署的禮品服務(wù),抽獎(jiǎng)服務(wù)如果發(fā)現(xiàn)中獎(jiǎng)了需要調(diào)用禮品服務(wù)去發(fā)放禮品。
如下圖所示:
4、負(fù)載均衡層的限流
4.1 防止用戶(hù)重復(fù)抽獎(jiǎng)
首先第一次在負(fù)載均衡層可以做的事情,就是防止重復(fù)抽獎(jiǎng)。
我們可以在負(fù)載均衡設(shè)備中做一些配置,判斷如果同一個(gè)用戶(hù)在1分鐘之內(nèi)多次發(fā)送請(qǐng)求來(lái)進(jìn)行抽獎(jiǎng),就認(rèn)為是惡意重復(fù)抽獎(jiǎng),或者是他們自己寫(xiě)的腳本在刷獎(jiǎng),這種流量一律認(rèn)為是無(wú)效流量,在負(fù)載均衡設(shè)備那個(gè)層次就給直接屏蔽掉。
舉個(gè)例子,比如有幾十萬(wàn)用戶(hù)瞬間同時(shí)抽獎(jiǎng),最多其實(shí)也就幾十萬(wàn)請(qǐng)求而已,但是如果有人重復(fù)抽獎(jiǎng)或者是寫(xiě)腳本刷獎(jiǎng),那可能瞬間涌入的是幾百萬(wàn)的請(qǐng)求,就不是幾十萬(wàn)的請(qǐng)求了,所以這里就可以把無(wú)效流量給攔截掉。
如下圖所示:
4.2 全部開(kāi)獎(jiǎng)后暴力攔截流量
其實(shí)秒殺、搶紅包、抽獎(jiǎng),這類(lèi)系統(tǒng)有一個(gè)共同的特點(diǎn),那就是假設(shè)有50萬(wàn)請(qǐng)求涌入進(jìn)來(lái),可能前5萬(wàn)請(qǐng)求就直接把事兒干完了,甚至是前500請(qǐng)求就把事兒干完了,后續(xù)的幾十萬(wàn)流量是無(wú)效的,不需要讓他們進(jìn)入后臺(tái)系統(tǒng)執(zhí)行業(yè)務(wù)邏輯了。
什么意思呢?
舉個(gè)例子,秒殺商品,假設(shè)有50萬(wàn)人搶一個(gè)特價(jià)手機(jī),人家就準(zhǔn)備了100臺(tái)手機(jī),那么50萬(wàn)請(qǐng)求瞬間涌入,其實(shí)前500個(gè)請(qǐng)求就把手機(jī)搶完了,后續(xù)的幾十萬(wàn)請(qǐng)求沒(méi)必要讓他轉(zhuǎn)發(fā)到Tomcat服務(wù)中去執(zhí)行秒殺業(yè)務(wù)邏輯了,不是嗎?
抽獎(jiǎng)、紅包都是一樣的 ,可能50萬(wàn)請(qǐng)求涌入,但是前1萬(wàn)個(gè)請(qǐng)求就把獎(jiǎng)品都抽完了,或者把紅包都搶完了,后續(xù)的流量其實(shí)已經(jīng)不需要放到Tomcat抽獎(jiǎng)服務(wù)上去了,直接暴力攔截返回抽獎(jiǎng)結(jié)束就可以了。
這樣的話(huà),其實(shí)在負(fù)載均衡這一層(可以考慮用Nginx之類(lèi)的來(lái)實(shí)現(xiàn))就可以攔截掉99%的無(wú)效流量。
所以必須讓抽獎(jiǎng)服務(wù)跟負(fù)載均衡之間有一個(gè)狀態(tài)共享的機(jī)制。
就是說(shuō)抽獎(jiǎng)服務(wù)一旦全部開(kāi)獎(jiǎng)完畢,直接更新一個(gè)共享狀態(tài)。然后負(fù)載均衡感知到了之后,后續(xù)請(qǐng)求全部攔截掉返回一個(gè)抽獎(jiǎng)結(jié)束的標(biāo)識(shí)就可以了。
這么做可能就會(huì)做到50萬(wàn)人一起請(qǐng)求,結(jié)果就可能2萬(wàn)請(qǐng)求到了后臺(tái)的Tomcat抽獎(jiǎng)服務(wù)中,48萬(wàn)請(qǐng)求直接攔截掉了。
我們可以基于redis來(lái)實(shí)現(xiàn)這種共享抽獎(jiǎng)狀態(tài),它非常輕量級(jí),很適合兩個(gè)層次的系統(tǒng)的共享訪(fǎng)問(wèn)。
當(dāng)然其實(shí)用ZooKeeper也是可以的,在負(fù)載均衡層可以基于zk客戶(hù)端監(jiān)聽(tīng)某個(gè)znode節(jié)點(diǎn)狀態(tài)。一旦抽獎(jiǎng)結(jié)束,抽獎(jiǎng)服務(wù)更新zk狀態(tài),負(fù)載均衡層會(huì)感知到。
下圖展示了上述所說(shuō)的過(guò)程:
5、Tomcat線(xiàn)程數(shù)量的優(yōu)化
其次就是對(duì)于線(xiàn)上生產(chǎn)環(huán)境的Tomcat,有一個(gè)至關(guān)重要的參數(shù)是需要根據(jù)自己的情況調(diào)節(jié)好的,那就是他的工作線(xiàn)程數(shù)量。
眾所周知,對(duì)于進(jìn)入Tomcat的每個(gè)請(qǐng)求,其實(shí)都會(huì)交給一個(gè)獨(dú)立的工作線(xiàn)程來(lái)進(jìn)行處理,那么Tomcat有多少線(xiàn)程,就決定了并發(fā)請(qǐng)求處理的能力。
但是這個(gè)線(xiàn)程數(shù)量是需要經(jīng)過(guò)壓測(cè)來(lái)進(jìn)行判斷的,因?yàn)槊總€(gè)線(xiàn)程都會(huì)處理一個(gè)請(qǐng)求,這個(gè)請(qǐng)求又需要訪(fǎng)問(wèn)數(shù)據(jù)庫(kù)之類(lèi)的外部系統(tǒng),所以不是每個(gè)系統(tǒng)的參數(shù)都可以一樣的,需要自己對(duì)系統(tǒng)進(jìn)行壓測(cè)。
但是給一個(gè)經(jīng)驗(yàn)值的話(huà),Tomcat的線(xiàn)程數(shù)量不宜過(guò)多。因?yàn)榫€(xiàn)程過(guò)多,普通虛擬機(jī)的CPU是扛不住的,反而會(huì)導(dǎo)致機(jī)器CPU負(fù)載過(guò)高,最終崩潰。
同時(shí),Tomcat的線(xiàn)程數(shù)量也不宜太少,因?yàn)槿绻?00個(gè)線(xiàn)程,那么會(huì)導(dǎo)致無(wú)法充分利用Tomcat的線(xiàn)程資源和機(jī)器的CPU資源。
所以一般來(lái)說(shuō),Tomcat線(xiàn)程數(shù)量在200~500之間都是可以的,但是具體多少需要自己壓測(cè)一下,不斷的調(diào)節(jié)參數(shù),看具體的CPU負(fù)載以及線(xiàn)程執(zhí)行請(qǐng)求的一個(gè)效率。
在CPU負(fù)載尚可,以及請(qǐng)求執(zhí)行性能正常的情況下,盡可能提高一些線(xiàn)程數(shù)量。
但是如果到一個(gè)臨界值,發(fā)現(xiàn)機(jī)器負(fù)載過(guò)高,而且線(xiàn)程處理請(qǐng)求的速度開(kāi)始下降,說(shuō)明這臺(tái)機(jī)扛不住這么多線(xiàn)程并發(fā)執(zhí)行處理請(qǐng)求了,此時(shí)就不能繼續(xù)上調(diào)線(xiàn)程數(shù)量了。
6、基于Redis實(shí)現(xiàn)抽獎(jiǎng)業(yè)務(wù)邏輯
現(xiàn)在問(wèn)題又來(lái)了,雖然在負(fù)載均衡那個(gè)層面,已經(jīng)把比如50萬(wàn)流量中的48萬(wàn)都攔截掉了,但是可能還是會(huì)有2萬(wàn)流量進(jìn)入抽獎(jiǎng)服務(wù)
此時(shí)抽獎(jiǎng)服務(wù)自然是可以多機(jī)器來(lái)部署的,比如假設(shè)一臺(tái)Tomcat可以抗500請(qǐng)求,那么2萬(wàn)并發(fā)就是40臺(tái)機(jī)器。
如果你是基于云平臺(tái)來(lái)部署系統(tǒng)的,搞活動(dòng)臨時(shí)租用一批機(jī)器就可以了,活動(dòng)結(jié)束了機(jī)器立馬可以釋放掉,現(xiàn)在云平臺(tái)都很方便。
但是有個(gè)問(wèn)題,你的數(shù)據(jù)庫(kù)MySQL能抗住2萬(wàn)的并發(fā)請(qǐng)求嗎?
如果你基于MySQL來(lái)實(shí)現(xiàn)核心的抽獎(jiǎng)業(yè)務(wù)邏輯,40個(gè)Tomcat部署的抽獎(jiǎng)服務(wù)頻繁對(duì)MySQL進(jìn)行增刪改查,這一個(gè)MySQL實(shí)例也是很難抗住的。
所以此時(shí)還得把MySQL給替換成Redis,通常這種場(chǎng)景下,建議是基于Redis來(lái)實(shí)現(xiàn)核心的業(yè)務(wù)邏輯。
Redis單機(jī)抗2萬(wàn)并發(fā)那是很輕松的一件事情,所以在這里又需要做進(jìn)一步的優(yōu)化。如下圖:
7、發(fā)放禮品環(huán)節(jié)進(jìn)行限流削峰
接著問(wèn)題又來(lái)了,假設(shè)抽獎(jiǎng)服務(wù)在2萬(wàn)請(qǐng)求中有1萬(wàn)請(qǐng)求抽中了獎(jiǎng)品,那么勢(shì)必會(huì)造成抽獎(jiǎng)服務(wù)對(duì)禮品服務(wù)調(diào)用1萬(wàn)次。
禮品服務(wù)假設(shè)也是優(yōu)化后的Tomcat,可以抗500并發(fā),難道禮品服務(wù)也要去部署20臺(tái)機(jī)器嗎?
其實(shí)這是沒(méi)必要的,因?yàn)槌楠?jiǎng)之后完全可以讓禮品服務(wù)在后臺(tái)慢慢的把中獎(jiǎng)的禮品給發(fā)放出去,不需要一下子就立馬對(duì)1萬(wàn)個(gè)請(qǐng)求完成禮品的發(fā)放邏輯。
所以這里可以在抽獎(jiǎng)服務(wù)和禮品服務(wù)之間,引入消息中間件,進(jìn)行限流削峰。
也就是說(shuō),抽獎(jiǎng)服務(wù)把中獎(jiǎng)信息發(fā)送到MQ,然后禮品服務(wù)假設(shè)就部署兩個(gè)Tomcat,慢慢的從MQ中消費(fèi)中獎(jiǎng)消息,然后慢慢完成1完禮品的發(fā)放就可以了。
假設(shè)兩個(gè)禮品服務(wù)實(shí)例每秒可以完成100個(gè)禮品的發(fā)放,那么1萬(wàn)個(gè)禮品也就是延遲100秒發(fā)放完畢罷了。
也就是你抽獎(jiǎng)之后,可能過(guò)了一兩分鐘,會(huì)看到自己的禮品發(fā)放的一些物流配送的進(jìn)度之類(lèi)的。
而且禮品服務(wù)可能需要在MySQL數(shù)據(jù)庫(kù)中做很多增刪改查的操作,比如插入中獎(jiǎng)紀(jì)錄,然后進(jìn)行禮品發(fā)貨等等。
此時(shí)因?yàn)槎Y品服務(wù)就2個(gè)Tomcat實(shí)例,所以對(duì)MySQL的并發(fā)讀寫(xiě)不會(huì)太高,那么數(shù)據(jù)庫(kù)層面也是可以抗住的。
整個(gè)過(guò)程,如下圖所示:
8、系統(tǒng)架構(gòu)設(shè)計(jì)總結(jié)
其實(shí)對(duì)于商品秒殺、抽獎(jiǎng)活動(dòng)、搶紅包類(lèi)的系統(tǒng)而言,架構(gòu)設(shè)計(jì)的思路很多都是類(lèi)似的,核心思路都是對(duì)于這種瞬時(shí)超高流量的系統(tǒng),盡可能在負(fù)載均衡層就把99%的無(wú)效流量攔截掉
然后在1%的流量進(jìn)入核心業(yè)務(wù)服務(wù)后,此時(shí)每秒并發(fā)還是可能會(huì)上萬(wàn),那么可以基于Redis實(shí)現(xiàn)核心業(yè)務(wù)邏輯 ,抗住上萬(wàn)并發(fā)。
最后對(duì)于類(lèi)似秒殺商品發(fā)貨、抽獎(jiǎng)商品發(fā)貨、紅包資金轉(zhuǎn)賬之類(lèi)的非常耗時(shí)的操作,完全可以基于MQ來(lái)限流削峰,后臺(tái)有一個(gè)服務(wù)慢慢執(zhí)行即可。






