如何設計一個百萬級使用者的抽獎系統?

石杉的架構筆記發表於2019-05-22
公眾號:狸貓技術窩

作者:愛釣魚的桌子哥,資深架構師


1、抽獎系統的背景引入

本文給大家分享一個之前經歷過的抽獎系統的流量削峰架構的設計方案。

抽獎、搶紅包、秒殺,這類系統其實都有一些共同的特點,那就是在某個時間點會瞬間湧入大量的人來點選系統,給系統造成瞬間高於平時百倍、千倍甚至幾十萬倍的流量壓力。

比如抽獎,有一種場景:某個網站或者APP規定好了在某個時間點,所有人都可以參與抽獎,那麼可能百萬級的使用者會蹲守在那個時間點,到時間大家一起參與這個抽獎。

搶紅包,可能是某個電視節目上,突然說掃碼可以搶紅包,那麼電視機前可能千萬級的使用者會瞬間一起開啟手機掃碼搶紅包。

秒殺更是如此,所謂秒殺,意思是讓大家都在電腦前等著,在某個時間突然就可以搶購某個限量的商品

比如某個手機平時賣5999,現在限量100臺價格才2999,50%的折扣,可能百萬級的使用者就會蹲守在電腦前在比如凌晨12點一起點選按鈕搶購這款手機。

類似的場景其實現在是很多的,那麼本文就用一個抽獎系統舉例,說說應對這種瞬時超高併發的流量,應該如何設計流量削峰的架構來應對,才能保證系統不會突然跨掉?


2、結合具體業務需求分析抽獎系統

假設現在有一個抽獎的業務場景,使用者在某個時間可以參與抽獎,比如一共有1萬個獎,獎品就是某個禮物。

然後參與抽獎的使用者可能有幾十萬,一瞬間可能幾十萬請求湧入過來,接著瞬間其中1萬人中獎了,剩餘的人都是沒中獎的。然後中獎的1萬人的請求會聯動呼叫禮品服務,完成這1萬中獎人的禮品發放。

簡單來說,需求場景就是如此,然而這裡就有很多的地方值得優化了。


3、一個未經過優化的系統架構

先來看一個未經過任何優化的系統架構,簡單來說就是有一個負載均衡的裝置會把瞬間湧入的超高併發的流量轉發到後臺的抽獎服務上。

這個抽獎服務就是用普通的Tomcat來部署的,裡面實現了具體的抽獎邏輯,假設剛開始最常規的抽獎邏輯是基於MySQL來實現的,接著就是基於Tomcat部署的禮品服務,抽獎服務如果發現中獎了需要呼叫禮品服務去發放禮品。

如下圖所示:

如何設計一個百萬級使用者的抽獎系統?


4、負載均衡層的限流

4.1 防止使用者重複抽獎

首先第一次在負載均衡層可以做的事情,就是防止重複抽獎。

我們可以在負載均衡裝置中做一些配置,判斷如果同一個使用者在1分鐘之內多次傳送請求來進行抽獎,就認為是惡意重複抽獎,或者是他們自己寫的指令碼在刷獎,這種流量一律認為是無效流量,在負載均衡裝置那個層次就給直接遮蔽掉。

舉個例子,比如有幾十萬使用者瞬間同時抽獎,最多其實也就幾十萬請求而已,但是如果有人重複抽獎或者是寫指令碼刷獎,那可能瞬間湧入的是幾百萬的請求,就不是幾十萬的請求了,所以這裡就可以把無效流量給攔截掉。

如下圖所示:

如何設計一個百萬級使用者的抽獎系統?

4.2 全部開獎後暴力攔截流量

其實秒殺、搶紅包、抽獎,這類系統有一個共同的特點,那就是假設有50萬請求湧入進來,可能前5萬請求就直接把事兒幹完了,甚至是前500請求就把事兒幹完了,後續的幾十萬流量是無效的,不需要讓他們進入後臺系統執行業務邏輯了。

什麼意思呢?

舉個例子,秒殺商品,假設有50萬人搶一個特價手機,人家就準備了100臺手機,那麼50萬請求瞬間湧入,其實前500個請求就把手機搶完了,後續的幾十萬請求沒必要讓他轉發到Tomcat服務中去執行秒殺業務邏輯了,不是嗎?

抽獎、紅包都是一樣的 ,可能50萬請求湧入,但是前1萬個請求就把獎品都抽完了,或者把紅包都搶完了,後續的流量其實已經不需要放到Tomcat抽獎服務上去了,直接暴力攔截返回抽獎結束就可以了。

這樣的話,其實在負載均衡這一層(可以考慮用Nginx之類的來實現)就可以攔截掉99%的無效流量。

所以必須讓抽獎服務跟負載均衡之間有一個狀態共享的機制。

就是說抽獎服務一旦全部開獎完畢,直接更新一個共享狀態。然後負載均衡感知到了之後,後續請求全部攔截掉返回一個抽獎結束的標識就可以了。

這麼做可能就會做到50萬人一起請求,結果就可能2萬請求到了後臺的Tomcat抽獎服務中,48萬請求直接攔截掉了。

我們可以基於Redis來實現這種共享抽獎狀態,它非常輕量級,很適合兩個層次的系統的共享訪問。

當然其實用ZooKeeper也是可以的,在負載均衡層可以基於zk客戶端監聽某個znode節點狀態。一旦抽獎結束,抽獎服務更新zk狀態,負載均衡層會感知到。

下圖展示了上述所說的過程:

如何設計一個百萬級使用者的抽獎系統?


5、Tomcat執行緒數量的優化

其次就是對於線上生產環境的Tomcat,有一個至關重要的引數是需要根據自己的情況調節好的,那就是他的工作執行緒數量。

眾所周知,對於進入Tomcat的每個請求,其實都會交給一個獨立的工作執行緒來進行處理,那麼Tomcat有多少執行緒,就決定了併發請求處理的能力。

但是這個執行緒數量是需要經過壓測來進行判斷的,因為每個執行緒都會處理一個請求,這個請求又需要訪問資料庫之類的外部系統,所以不是每個系統的引數都可以一樣的,需要自己對系統進行壓測。

但是給一個經驗值的話,Tomcat的執行緒數量不宜過多。因為執行緒過多,普通虛擬機器的CPU是扛不住的,反而會導致機器CPU負載過高,最終崩潰。

同時,Tomcat的執行緒數量也不宜太少,因為如果就100個執行緒,那麼會導致無法充分利用Tomcat的執行緒資源和機器的CPU資源。

所以一般來說,Tomcat執行緒數量在200~500之間都是可以的,但是具體多少需要自己壓測一下,不斷的調節引數,看具體的CPU負載以及執行緒執行請求的一個效率。

在CPU負載尚可,以及請求執行效能正常的情況下,儘可能提高一些執行緒數量。

但是如果到一個臨界值,發現機器負載過高,而且執行緒處理請求的速度開始下降,說明這臺機扛不住這麼多執行緒併發執行處理請求了,此時就不能繼續上調執行緒數量了。

如何設計一個百萬級使用者的抽獎系統?


6、基於Redis實現抽獎業務邏輯

現在問題又來了,雖然在負載均衡那個層面,已經把比如50萬流量中的48萬都攔截掉了,但是可能還是會有2萬流量進入抽獎服務

此時抽獎服務自然是可以多機器來部署的,比如假設一臺Tomcat可以抗500請求,那麼2萬併發就是40臺機器。

如果你是基於雲平臺來部署系統的,搞活動臨時租用一批機器就可以了,活動結束了機器立馬可以釋放掉,現在雲平臺都很方便。

但是有個問題,你的資料庫MySQL能抗住2萬的併發請求嗎?

如果你基於MySQL來實現核心的抽獎業務邏輯,40個Tomcat部署的抽獎服務頻繁對MySQL進行增刪改查,這一個MySQL例項也是很難抗住的。

所以此時還得把MySQL給替換成Redis,通常這種場景下,建議是基於Redis來實現核心的業務邏輯。

Redis單機抗2萬併發那是很輕鬆的一件事情,所以在這裡又需要做進一步的優化。如下圖:

如何設計一個百萬級使用者的抽獎系統?


7、發放禮品環節進行限流削峰

接著問題又來了,假設抽獎服務在2萬請求中有1萬請求抽中了獎品,那麼勢必會造成抽獎服務對禮品服務呼叫1萬次。

禮品服務假設也是優化後的Tomcat,可以抗500併發,難道禮品服務也要去部署20臺機器嗎?

其實這是沒必要的,因為抽獎之後完全可以讓禮品服務在後臺慢慢的把中獎的禮品給發放出去,不需要一下子就立馬對1萬個請求完成禮品的發放邏輯。

所以這裡可以在抽獎服務和禮品服務之間,引入訊息中介軟體,進行限流削峰

也就是說,抽獎服務把中獎資訊傳送到MQ,然後禮品服務假設就部署兩個Tomcat,慢慢的從MQ中消費中獎訊息,然後慢慢完成1完禮品的發放就可以了。

假設兩個禮品服務例項每秒可以完成100個禮品的發放,那麼1萬個禮品也就是延遲100秒發放完畢罷了。

也就是你抽獎之後,可能過了一兩分鐘,會看到自己的禮品發放的一些物流配送的進度之類的。

而且禮品服務可能需要在MySQL資料庫中做很多增刪改查的操作,比如插入中獎紀錄,然後進行禮品發貨等等。

此時因為禮品服務就2個Tomcat例項,所以對MySQL的併發讀寫不會太高,那麼資料庫層面也是可以抗住的。

整個過程,如下圖所示:

如何設計一個百萬級使用者的抽獎系統?


8、系統架構設計總結

其實對於商品秒殺、抽獎活動、搶紅包類的系統而言,架構設計的思路很多都是類似的,核心思路都是對於這種瞬時超高流量的系統,儘可能在負載均衡層就把99%的無效流量攔截掉

然後在1%的流量進入核心業務服務後,此時每秒併發還是可能會上萬,那麼可以基於Redis實現核心業務邏輯 ,抗住上萬併發。

最後對於類似秒殺商品發貨、抽獎商品發貨、紅包資金轉賬之類的非常耗時的操作,完全可以基於MQ來限流削峰,後臺有一個服務慢慢執行即可。


作者簡介:

愛釣魚的桌子哥,資深架構師

作者先後工作於滴滴、百度、位元組跳動等國內一線網際網路大廠,從事基礎架構相關工作。帶領團隊設計與構建了大規模的分散式儲存系統、分散式訊息中介軟體、分散式資料庫,對分散式架構設計、系統高可用體系構建、基礎中介軟體架構都有豐富的經驗。


END

長按下圖二維碼,即刻關注【狸貓技術窩】 阿里、京東、美團、位元組跳動 頂尖技術專家坐鎮 為IT人打造一個 “有溫度” 的技術窩!

如何設計一個百萬級使用者的抽獎系統?


相關文章