17 | 消息隊列:秒殺時如何處理每秒上萬次的下單請求?

在課程一開始,我就帶你了解了高并發(fā)系統(tǒng)設(shè)計的三個目標:性能、可用性和可擴展性,而在提升系統(tǒng)性能方面我們一直關(guān)注的是系統(tǒng)的查詢性能,也用了很多的篇幅去講解數(shù)據(jù)庫的分布式改造,各類緩存的原理和使用技巧。究其原因在于我們遇到的大部分場景都是讀多寫少,尤其是在一個系統(tǒng)的初級階段。

比如一個社區(qū)的系統(tǒng)初期一定是只有少量的種子用戶在生產(chǎn)內(nèi)容,而大部分的用戶都在“圍觀”別人在說什么。此時,整體的流量比較小,而寫流量可能只占整體流量的百分之一,那么即使整體的 QPS 到了 10000 次 / 秒,寫請求也只是到了每秒 100 次,如果要對寫請求做性能優(yōu)化,它的性價比確實不太高。

但隨著業(yè)務(wù)發(fā)展,你可能會遇到一些存在高并發(fā)寫請求的場景,其中秒殺搶購就是最典型的場景。假設(shè)你的商城策劃了一期秒殺活動,活動在第五天的 00:00 開始,僅限前 200 名,那么秒殺即將開始時,后臺會顯示用戶正在瘋狂地刷新 APP 或者瀏覽器來保證自己能夠盡量早的看到商品。

這時,你面對的依舊是讀請求過高,那么應(yīng)對的措施有哪些呢?

因為用戶查詢的是少量的商品數(shù)據(jù),屬于查詢的熱點數(shù)據(jù),你可以采用緩存策略將請求盡量擋在上層的緩存中,能被靜態(tài)化的數(shù)據(jù)(比如商城里的圖片和視頻數(shù)據(jù))盡量做到靜態(tài)化,這樣就可以命中 CDN 節(jié)點緩存減少 Web 服務(wù)器的查詢量和帶寬負擔。Web 服務(wù)器比如 Nginx 可以直接訪問分布式緩存節(jié)點,從而避免請求到達 Tomcat 等業(yè)務(wù)服務(wù)器。

當然,你可以加上一些限流的策略,比如對短時間之內(nèi)來自某一個用戶、某一個 IP 或者某一臺設(shè)備的重復(fù)請求做丟棄處理。

通過這幾種方式,請求就可以盡量擋在數(shù)據(jù)庫之外了。

稍微緩解了讀請求之后,00:00 分秒殺活動準時開始,用戶瞬間向電商系統(tǒng)請求生成訂單,扣減庫存,用戶的這些寫操作都是不經(jīng)過緩存直達數(shù)據(jù)庫的。1 秒鐘之內(nèi),有 1 萬個數(shù)據(jù)庫連接同時達到,系統(tǒng)的數(shù)據(jù)庫瀕臨崩潰,尋找能夠應(yīng)對如此高并發(fā)的寫請求方案迫在眉睫。這時你想到了消息隊列。

我所理解的消息隊列

你應(yīng)該已經(jīng)了解消息隊列到底是什么了,所以我不再講解它的概念,只聊聊自己對消息隊列的看法。我在歷年的工作經(jīng)歷中,一直把消息隊列看作暫時存儲數(shù)據(jù)的一個容器,認為它是一個平衡低速系統(tǒng)和高速系統(tǒng)處理任務(wù)時間差的工具,我給你舉個形象的例子。

比如古代的臣子經(jīng)常去朝見皇上陳述一些國家大事,等著皇上拍板做決策。但是大臣很多,如果同時去找皇上,你說一句我說一句,皇上肯定會崩潰。后來變成臣子到了午門之后要原地等著皇上將他們一個一個地召見進大殿商議國事,這樣就可以緩解皇上處理事情的壓力了。你可以把午門看作一個暫時容納臣子的容器,也就是我們所說的消息隊列。

其實你在一些組件中都會看到消息隊列的影子:

  • 在 Java 線程池中我們就會使用一個隊列來暫時存儲提交的任務(wù),等待有空閑的線程處理這些任務(wù);
  • 操作系統(tǒng)中,中斷的下半部分也會使用工作隊列來實現(xiàn)延后執(zhí)行;
  • 我們在實現(xiàn)一個 RPC 框架時,也會將從網(wǎng)絡(luò)上接收到的請求寫到隊列里,再啟動若干個工作線程來處理。

總之,隊列是在系統(tǒng)設(shè)計時一種常見的組件。

那么我們?nèi)绾斡孟㈥犃薪鉀Q秒殺場景下的問題呢?接下來,我們結(jié)合具體的例子來看看消息隊列在秒殺場景下起到的作用。

削去秒殺場景下的峰值寫流量

剛才提到,在秒殺場景下短時間之內(nèi)數(shù)據(jù)庫的寫流量會很高,那么依照我們以前的思路應(yīng)該對數(shù)據(jù)做分庫分表。如果已經(jīng)做了分庫分表,那么就需要擴展更多的數(shù)據(jù)庫來應(yīng)對更高的寫流量。但是無論是分庫分表還是擴充更多的數(shù)據(jù)庫都會比較復(fù)雜,原因是你需要將數(shù)據(jù)庫中的數(shù)據(jù)做遷移,這個時間就要按天甚至按周來計算了。

而在秒殺場景下高并發(fā)的寫請求并不是持續(xù)的,也不是經(jīng)常發(fā)生的,而只有在秒殺活動開始后的幾秒或者十幾秒時間內(nèi)才會存在。為了應(yīng)對這十幾秒的瞬間寫高峰花費幾天甚至幾周的時間來擴容數(shù)據(jù)庫,再在秒殺之后花費幾天的時間來做縮容,這無疑是得不償失的。

所以我們的思路是:將秒殺請求暫存在消息隊列中,然后業(yè)務(wù)服務(wù)器會響應(yīng)用戶“秒殺結(jié)果正在計算中”,釋放了系統(tǒng)資源之后再處理其它用戶的請求。

我們會在后臺啟動若干個隊列處理程序消費消息隊列中的消息,再執(zhí)行校驗庫存、下單等邏輯。因為只有有限個隊列處理線程在執(zhí)行,所以落入后端數(shù)據(jù)庫上的并發(fā)請求是有限的。而請求是可以在消息隊列中被短暫地堆積,當庫存被消耗完之后,消息隊列中堆積的請求就可以被丟棄了。

img

這就是消息隊列在秒殺系統(tǒng)中最主要的作用:削峰填谷,也就是說它可以削平短暫的流量高峰,雖說堆積會造成請求被短暫延遲處理,但是只要我們時刻監(jiān)控消息隊列中的堆積長度,在堆積量超過一定量時,增加隊列處理機數(shù)量來提升消息的處理能力就好了,而且秒殺的用戶對于短暫延遲知曉秒殺的結(jié)果也是有一定容忍度的。

這里需要注意一下,我所說的是“短暫”延遲,如果長時間沒有給用戶公示秒殺結(jié)果,那么用戶可能就會懷疑你的秒殺活動有貓膩了。所以在使用消息隊列應(yīng)對流量峰值時,需要對隊列處理的時間、前端寫入流量的大小、數(shù)據(jù)庫處理能力做好評估,然后根據(jù)不同的量級來決定部署多少臺隊列處理程序。

比如你的秒殺商品有 1000 件,處理一次購買請求的時間是 500ms,那么總共就需要 500s 的時間。這時你部署 10 個隊列處理程序,那么秒殺請求的處理時間就是 50s,也就是說用戶需要等待 50s 才可以看到秒殺的結(jié)果,這是可以接受的。這時會并發(fā) 10 個請求到達數(shù)據(jù)庫,并不會對數(shù)據(jù)庫造成很大的壓力。

通過異步處理簡化秒殺請求中的業(yè)務(wù)流程

其實在大量的寫請求“攻擊”你的電商系統(tǒng)的時候,消息隊列除了發(fā)揮主要的削峰填谷的作用之外,還可以實現(xiàn)異步處理來簡化秒殺請求中的業(yè)務(wù)流程,提升系統(tǒng)的性能。

你想,在剛才提到的秒殺場景下,我們在處理購買請求時需要 500ms。這時你分析了一下整個的購買流程,發(fā)現(xiàn)這里面會有主要的業(yè)務(wù)邏輯,也會有次要的業(yè)務(wù)邏輯:比如說,主要的流程是生成訂單、扣減庫存;次要的流程可能是我們在下單購買成功之后會給用戶發(fā)放優(yōu)惠券,會增加用戶的積分。

假如發(fā)放優(yōu)惠券的耗時是 50ms,增加用戶積分的耗時也是 50ms,那么如果我們將發(fā)放優(yōu)惠券、增加積分的操作放在另外一個隊列處理機中執(zhí)行,那么整個流程就縮短到了 400ms,性能提升了 20%,處理這 1000 件商品的時間就變成了 400s。如果我們還是希望能在 50s 之內(nèi)看到秒殺結(jié)果的話,只需要部署 8 個隊列程序就好了。 經(jīng)過將一些業(yè)務(wù)流程異步處理之后,我們的秒殺系統(tǒng)部署結(jié)構(gòu)也會有所改變:

img

解耦實現(xiàn)秒殺系統(tǒng)模塊之間松耦合

除了異步處理和削峰填谷以外,消息隊列在秒殺系統(tǒng)中起到的另一個作用是解耦合。

比如數(shù)據(jù)團隊對你說,在秒殺活動之后想要統(tǒng)計活動的數(shù)據(jù),借此來分析活動商品的受歡迎程度、購買者人群的特點以及用戶對于秒殺互動的滿意程度等等指標。而我們需要將大量的數(shù)據(jù)發(fā)送給數(shù)據(jù)團隊,那么要怎么做呢?

一個思路是:使用 HTTP 或者 RPC 的方式來同步地調(diào)用,也就是數(shù)據(jù)團隊這邊提供一個接口,我們實時將秒殺的數(shù)據(jù)推送給它,但是這樣調(diào)用會有兩個問題:

1、整體系統(tǒng)的耦合性比較強,當數(shù)據(jù)團隊的接口發(fā)生故障時,會影響到秒殺系統(tǒng)的可用性。

2、當數(shù)據(jù)系統(tǒng)需要新的字段,就要變更接口的參數(shù),那么秒殺系統(tǒng)也要隨著一起變更。

這時,我們可以考慮使用消息隊列降低業(yè)務(wù)系統(tǒng)和數(shù)據(jù)系統(tǒng)的直接耦合度。

秒殺系統(tǒng)產(chǎn)生一條購買數(shù)據(jù)后,我們可以先把全部數(shù)據(jù)發(fā)送給消息隊列,然后數(shù)據(jù)團隊再訂閱這個消息隊列的話題,這樣它們就可以接收到數(shù)據(jù),然后再做過濾和處理了。

秒殺系統(tǒng)在這樣解耦合之后,數(shù)據(jù)系統(tǒng)的故障就不會影響到秒殺系統(tǒng)了,同時當數(shù)據(jù)系統(tǒng)需要新的字段時,只需要解析消息隊列中的消息,拿到需要的數(shù)據(jù)就好了。

img

異步處理、解耦合和削峰填谷是消息隊列在秒殺系統(tǒng)設(shè)計中起到的主要作用,其中異步處理可以簡化業(yè)務(wù)流程中的步驟,提升系統(tǒng)性能;削峰填谷可以削去到達秒殺系統(tǒng)的峰值流量,讓業(yè)務(wù)邏輯的處理更加緩和;解耦合可以將秒殺系統(tǒng)和數(shù)據(jù)系統(tǒng)解耦開,這樣兩個系統(tǒng)的任何變更都不會影響到另一個系統(tǒng),

如果你的系統(tǒng)想要提升寫入性能實現(xiàn)系統(tǒng)的低耦合,想要抵擋高并發(fā)的寫流量,那么你就可以考慮使用消息隊列來完成。

總結(jié)

1、削峰填谷是消息隊列的主要作用

2、分離業(yè)務(wù)異步處理更能增加大系統(tǒng)處理能力

3、解耦可以提升系統(tǒng)的魯棒性

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容