【高并發(fā)】秒殺系統(tǒng)架構(gòu)解密扰肌,不是所有的秒殺都是秒殺(升級(jí)版)E浊蕖!

寫在前面

很多小伙伴反饋說曙旭,高并發(fā)專題學(xué)了那么久墩剖,但是,在真正做項(xiàng)目時(shí)夷狰,仍然不知道如何下手處理高并發(fā)業(yè)務(wù)場(chǎng)景岭皂!甚至很多小伙伴仍然停留在只是簡(jiǎn)單的提供接口(CRUD)階段,不知道學(xué)習(xí)的并發(fā)知識(shí)如何運(yùn)用到實(shí)際項(xiàng)目中沼头,就更別提如何構(gòu)建高并發(fā)系統(tǒng)了爷绘!

究竟什么樣的系統(tǒng)算是高并發(fā)系統(tǒng)书劝?今天,我們就一起解密高并發(fā)業(yè)務(wù)場(chǎng)景下典型的秒殺系統(tǒng)的架構(gòu)土至,結(jié)合高并發(fā)專題下的其他文章购对,學(xué)以致用。

電商系統(tǒng)架構(gòu)

在電商領(lǐng)域陶因,存在著典型的秒殺業(yè)務(wù)場(chǎng)景骡苞,那何謂秒殺場(chǎng)景呢。簡(jiǎn)單的來說就是一件商品的購買人數(shù)遠(yuǎn)遠(yuǎn)大于這件商品的庫存楷扬,而且這件商品在很短的時(shí)間內(nèi)就會(huì)被搶購一空解幽。 比如每年的618、雙11大促烘苹,小米新品促銷等業(yè)務(wù)場(chǎng)景躲株,就是典型的秒殺業(yè)務(wù)場(chǎng)景。

我們可以將電商系統(tǒng)的架構(gòu)簡(jiǎn)化成下圖所示镣衡。

由圖所示霜定,我們可以簡(jiǎn)單的將電商系統(tǒng)的核心層分為:負(fù)載均衡層、應(yīng)用層和持久層廊鸥。接下來望浩,我們就預(yù)估下每一層的并發(fā)量。

  • 假如負(fù)載均衡層使用的是高性能的Nginx惰说,則我們可以預(yù)估Nginx最大的并發(fā)度為:10W+曾雕,這里是以萬為單位。

  • 假設(shè)應(yīng)用層我們使用的是Tomcat助被,而Tomcat的最大并發(fā)度可以預(yù)估為800左右剖张,這里是以百為單位。

  • 假設(shè)持久層的緩存使用的是Redis揩环,數(shù)據(jù)庫使用的是MySQL搔弄,MySQL的最大并發(fā)度可以預(yù)估為1000左右,以千為單位丰滑。Redis的最大并發(fā)度可以預(yù)估為5W左右顾犹,以萬為單位。

所以褒墨,負(fù)載均衡層炫刷、應(yīng)用層和持久層各自的并發(fā)度是不同的,那么郁妈,為了提升系統(tǒng)的總體并發(fā)度和緩存浑玛,我們通常可以采取哪些方案呢噩咪?

(1)系統(tǒng)擴(kuò)容

系統(tǒng)擴(kuò)容包括垂直擴(kuò)容和水平擴(kuò)容顾彰,增加設(shè)備和機(jī)器配置极阅,絕大多數(shù)的場(chǎng)景有效。

(2)緩存

本地緩存或者集中式緩存涨享,減少網(wǎng)絡(luò)IO筋搏,基于內(nèi)存讀取數(shù)據(jù)。大部分場(chǎng)景有效厕隧。

(3)讀寫分離

采用讀寫分離奔脐,分而治之,增加機(jī)器的并行處理能力吁讨。

秒殺系統(tǒng)的特點(diǎn)

對(duì)于秒殺系統(tǒng)來說髓迎,我們可以從業(yè)務(wù)和技術(shù)兩個(gè)角度來闡述其自身存在的一些特點(diǎn)。

秒殺系統(tǒng)的業(yè)務(wù)特點(diǎn)

這里挡爵,我們可以使用12306網(wǎng)站來舉例竖般,每年春運(yùn)時(shí)甚垦,12306網(wǎng)站的訪問量是非常大的茶鹃,但是網(wǎng)站平時(shí)的訪問量卻是比較平緩的,也就是說艰亮,每年春運(yùn)時(shí)節(jié)闭翩,12306網(wǎng)站的訪問量會(huì)出現(xiàn)瞬時(shí)突增的現(xiàn)象。

再比如迄埃,小米秒殺系統(tǒng)疗韵,在上午10點(diǎn)開售商品,10點(diǎn)前的訪問量比較平緩侄非,10點(diǎn)時(shí)同樣會(huì)出現(xiàn)并發(fā)量瞬時(shí)突增的現(xiàn)象蕉汪。

所以,秒殺系統(tǒng)的流量和并發(fā)量我們可以使用下圖來表示逞怨。

由圖可以看出者疤,秒殺系統(tǒng)的并發(fā)量存在瞬時(shí)凸峰的特點(diǎn),也叫做流量突刺現(xiàn)象叠赦。

我們可以將秒殺系統(tǒng)的特點(diǎn)總結(jié)如下驹马。

(1)限時(shí)、限量除秀、限價(jià)

在規(guī)定的時(shí)間內(nèi)進(jìn)行糯累;秒殺活動(dòng)中商品的數(shù)量有限;商品的價(jià)格會(huì)遠(yuǎn)遠(yuǎn)低于原來的價(jià)格册踩,也就是說泳姐,在秒殺活動(dòng)中,商品會(huì)以遠(yuǎn)遠(yuǎn)低于原來的價(jià)格出售暂吉。

例如仗岸,秒殺活動(dòng)的時(shí)間僅限于某天上午10點(diǎn)到10點(diǎn)半允耿,商品數(shù)量只有10萬件,售完為止扒怖,而且商品的價(jià)格非常低较锡,例如:1元購等業(yè)務(wù)場(chǎng)景。

限時(shí)盗痒、限量和限價(jià)可以單獨(dú)存在蚂蕴,也可以組合存在。

(2)活動(dòng)預(yù)熱

需要提前配置活動(dòng)俯邓;活動(dòng)還未開始時(shí)骡楼,用戶可以查看活動(dòng)的相關(guān)信息;秒殺活動(dòng)開始前稽鞭,對(duì)活動(dòng)進(jìn)行大力宣傳鸟整。

(3)持續(xù)時(shí)間短

購買的人數(shù)數(shù)量龐大;商品會(huì)迅速售完朦蕴。

在系統(tǒng)流量呈現(xiàn)上篮条,就會(huì)出現(xiàn)一個(gè)突刺現(xiàn)象,此時(shí)的并發(fā)訪問量是非常高的吩抓,大部分秒殺場(chǎng)景下涉茧,商品會(huì)在極短的時(shí)間內(nèi)售完。

秒殺系統(tǒng)的技術(shù)特點(diǎn)

我們可以將秒殺系統(tǒng)的技術(shù)特點(diǎn)總結(jié)如下疹娶。

(1)瞬時(shí)并發(fā)量非常高

大量用戶會(huì)在同一時(shí)間搶購商品伴栓;瞬間并發(fā)峰值非常高。

(2)讀多寫少

系統(tǒng)中商品頁的訪問量巨大雨饺;商品的可購買數(shù)量非常少钳垮;庫存的查詢?cè)L問數(shù)量遠(yuǎn)遠(yuǎn)大于商品的購買數(shù)量。

在商品頁中往往會(huì)加入一些限流措施额港,例如早期的秒殺系統(tǒng)商品頁會(huì)加入驗(yàn)證碼來平滑前端對(duì)系統(tǒng)的訪問流量饺窿,近期的秒殺系統(tǒng)商品詳情頁會(huì)在用戶打開頁面時(shí),提示用戶登錄系統(tǒng)锹安。這都是對(duì)系統(tǒng)的訪問進(jìn)行限流的一些措施短荐。

(3)流程簡(jiǎn)單

秒殺系統(tǒng)的業(yè)務(wù)流程一般比較簡(jiǎn)單;總體上來說叹哭,秒殺系統(tǒng)的業(yè)務(wù)流程可以概括為:下單減庫存忍宋。

秒殺三階段

通常,從秒殺開始到結(jié)束风罩,往往會(huì)經(jīng)歷三個(gè)階段:

  • 準(zhǔn)備階段:這個(gè)階段也叫作系統(tǒng)預(yù)熱階段糠排,此時(shí)會(huì)提前預(yù)熱秒殺系統(tǒng)的業(yè)務(wù)數(shù)據(jù),往往這個(gè)時(shí)候超升,用戶會(huì)不斷刷新秒殺頁面入宦,來查看秒殺活動(dòng)是否已經(jīng)開始哺徊。在一定程度上,通過用戶不斷刷新頁面的操作乾闰,可以將一些數(shù)據(jù)存儲(chǔ)到Redis中進(jìn)行預(yù)熱落追。
  • 秒殺階段:這個(gè)階段主要是秒殺活動(dòng)的過程,會(huì)產(chǎn)生瞬時(shí)的高并發(fā)流量涯肩,對(duì)系統(tǒng)資源會(huì)造成巨大的沖擊轿钠,所以,在秒殺階段一定要做好系統(tǒng)防護(hù)病苗。
  • 結(jié)算階段: 完成秒殺后的數(shù)據(jù)處理工作疗垛,比如數(shù)據(jù)的一致性問題處理,異常情況處理硫朦,商品的回倉處理等贷腕。

針對(duì)這種短時(shí)間內(nèi)大流量的系統(tǒng)來說,就不太適合使用系統(tǒng)擴(kuò)容了咬展,因?yàn)榧词瓜到y(tǒng)擴(kuò)容了泽裳,也就是在很短的時(shí)間內(nèi)會(huì)使用到擴(kuò)容后的系統(tǒng),大部分時(shí)間內(nèi)挚赊,系統(tǒng)無需擴(kuò)容即可正常訪問诡壁。 那么济瓢,我們可以采取哪些方案來提升系統(tǒng)的秒殺性能呢荠割?

秒殺系統(tǒng)方案

針對(duì)秒殺系統(tǒng)的特點(diǎn),我們可以采取如下的措施來提升系統(tǒng)的性能旺矾。

(1)異步解耦

將整體流程進(jìn)行拆解蔑鹦,核心流程通過隊(duì)列方式進(jìn)行控制。

(2)限流防刷

控制網(wǎng)站整體流量箕宙,提高請(qǐng)求的門檻嚎朽,避免系統(tǒng)資源耗盡。

(3)資源控制

將整體流程中的資源調(diào)度進(jìn)行控制柬帕,揚(yáng)長避短哟忍。

由于應(yīng)用層能夠承載的并發(fā)量比緩存的并發(fā)量少很多。所以陷寝,在高并發(fā)系統(tǒng)中锅很,我們可以直接使用OpenResty由負(fù)載均衡層訪問緩存,避免了調(diào)用應(yīng)用層的性能損耗凤跑。大家可以到https://openresty.org/cn/來了解有關(guān)OpenResty更多的知識(shí)爆安。同時(shí),由于秒殺系統(tǒng)中仔引,商品數(shù)量比較少扔仓,我們也可以使用動(dòng)態(tài)渲染技術(shù)褐奥,CDN技術(shù)來加速網(wǎng)站的訪問性能。

如果在秒殺活動(dòng)開始時(shí)翘簇,并發(fā)量太高時(shí)撬码,我們可以將用戶的請(qǐng)求放入隊(duì)列中進(jìn)行處理,并為用戶彈出排隊(duì)頁面版保。

<p align="right">注:圖片來自魅族</p>

秒殺系統(tǒng)時(shí)序圖

網(wǎng)上很多的秒殺系統(tǒng)和對(duì)秒殺系統(tǒng)的解決方案耍群,并不是真正的秒殺系統(tǒng),他們采用的只是同步處理請(qǐng)求的方案找筝,一旦并發(fā)量真的上來了蹈垢,他們所謂的秒殺系統(tǒng)的性能會(huì)急劇下降。我們先來看一下秒殺系統(tǒng)在同步下單時(shí)的時(shí)序圖袖裕。

同步下單流程

1.用戶發(fā)起秒殺請(qǐng)求

在同步下單流程中曹抬,首先,用戶發(fā)起秒殺請(qǐng)求急鳄。商城服務(wù)需要依次執(zhí)行如下流程來處理秒殺請(qǐng)求的業(yè)務(wù)谤民。

(1)識(shí)別驗(yàn)證碼是否正確

商城服務(wù)判斷用戶發(fā)起秒殺請(qǐng)求時(shí)提交的驗(yàn)證碼是否正確。

(2)判斷活動(dòng)是否已經(jīng)結(jié)束

驗(yàn)證當(dāng)前秒殺活動(dòng)是否已經(jīng)結(jié)束疾宏。

(3)驗(yàn)證訪問請(qǐng)求是否處于黑名單

在電商領(lǐng)域中张足,存在著很多的惡意競(jìng)爭(zhēng),也就是說坎藐,其他商家可能會(huì)通過不正當(dāng)手段來惡意請(qǐng)求秒殺系統(tǒng)为牍,占用系統(tǒng)大量的帶寬和其他系統(tǒng)資源。此時(shí)岩馍,就需要使用風(fēng)控系統(tǒng)等實(shí)現(xiàn)黑名單機(jī)制碉咆。為了簡(jiǎn)單,也可以使用攔截器統(tǒng)計(jì)訪問頻次實(shí)現(xiàn)黑名單機(jī)制蛀恩。

(4)驗(yàn)證真實(shí)庫存是否足夠

系統(tǒng)需要驗(yàn)證商品的真實(shí)庫存是否足夠疫铜,是否能夠支持本次秒殺活動(dòng)的商品庫存量。

(5)扣減緩存中的庫存

在秒殺業(yè)務(wù)中双谆,往往會(huì)將商品庫存等信息存放在緩存中壳咕,此時(shí),還需要驗(yàn)證秒殺活動(dòng)使用的商品庫存是否足夠顽馋,并且需要扣減秒殺活動(dòng)的商品庫存數(shù)量谓厘。

(6)計(jì)算秒殺的價(jià)格

由于在秒殺活動(dòng)中,商品的秒殺價(jià)格和商品的真實(shí)價(jià)格存在差異趣避,所以庞呕,需要計(jì)算商品的秒殺價(jià)格。

<font color="#FF0000">注意:如果在秒殺場(chǎng)景中,系統(tǒng)涉及的業(yè)務(wù)更加復(fù)雜的話住练,會(huì)涉及更多的業(yè)務(wù)操作地啰,這里,我只是列舉出一些常見的業(yè)務(wù)操作讲逛。</font>

2.提交訂單

(1)訂單入口

將用戶提交的訂單信息保存到數(shù)據(jù)庫中亏吝。

(2)扣減真實(shí)庫存

訂單入庫后,需要在商品的真實(shí)庫存中將本次成功下單的商品數(shù)量扣除盏混。

如果我們使用上述流程開發(fā)了一個(gè)秒殺系統(tǒng)蔚鸥,當(dāng)用戶發(fā)起秒殺請(qǐng)求時(shí),由于系統(tǒng)每個(gè)業(yè)務(wù)流程都是串行執(zhí)行的许赃,整體上系統(tǒng)的性能不會(huì)太高止喷,當(dāng)并發(fā)量太高時(shí),我們會(huì)為用戶彈出下面的排隊(duì)頁面混聊,來提示用戶進(jìn)行等待弹谁。

<p align="right">注:圖片來自魅族</p>

此時(shí)的排隊(duì)時(shí)間可能是15秒,也可能是30秒句喜,甚至是更長時(shí)間预愤。這就存在一個(gè)問題:在用戶發(fā)起秒殺請(qǐng)求到服務(wù)器返回結(jié)果的這段時(shí)間內(nèi),客戶端和服務(wù)器之間的連接不會(huì)被釋放咳胃,這就會(huì)占大量占用服務(wù)器的資源植康。

網(wǎng)上很多介紹如何實(shí)現(xiàn)秒殺系統(tǒng)的文章都是采用的這種方式,那么展懈,這種方式能做秒殺系統(tǒng)嗎销睁?答案是可以做,但是這種方式支撐的并發(fā)量并不是太高标沪。此時(shí)榄攀,有些網(wǎng)友可能會(huì)問:我們公司就是這樣做的秒殺系統(tǒng)笆雀怠金句!上線后一直在用,沒啥問題奥类帧违寞!我想說的是:使用同步下單方式確實(shí)可以做秒殺系統(tǒng),但是同步下單的性能不會(huì)太高偶房。之所以你們公司采用同步下單的方式做秒殺系統(tǒng)沒出現(xiàn)大的問題趁曼,那是因?yàn)槟銈兊拿霘⑾到y(tǒng)的并發(fā)量沒達(dá)到一定的量級(jí),也就是說棕洋,你們的秒殺系統(tǒng)的并發(fā)量其實(shí)并不高挡闰。

所以,很多所謂的秒殺系統(tǒng),存在著秒殺的業(yè)務(wù)摄悯,但是稱不上真正的秒殺系統(tǒng)赞季,原因就在于他們使用的是同步的下單流程,限制了系統(tǒng)的并發(fā)流量奢驯。之所以上線后沒出現(xiàn)太大的問題申钩,是因?yàn)橄到y(tǒng)的并發(fā)量不高,不足以壓死整個(gè)系統(tǒng)瘪阁。

如果12306撒遣、淘寶、天貓管跺、京東义黎、小米等大型商城的秒殺系統(tǒng)是這么玩的話,那么豁跑,他們的系統(tǒng)遲早會(huì)被玩死轩缤,他們的系統(tǒng)工程師不被開除才怪!所以贩绕,在秒殺系統(tǒng)中火的,這種同步處理下單的業(yè)務(wù)流程的方案是不可取的。

以上就是同步下單的整個(gè)流程操作淑倾,如果下單流程更加復(fù)雜的話馏鹤,就會(huì)涉及到更多的業(yè)務(wù)操作。

異步下單流程

既然同步下單流程的秒殺系統(tǒng)稱不上真正的秒殺系統(tǒng)娇哆,那我們就需要采用異步的下單流程了湃累。異步的下單流程不會(huì)限制系統(tǒng)的高并發(fā)流量。

1.用戶發(fā)起秒殺請(qǐng)求

用戶發(fā)起秒殺請(qǐng)求后碍讨,商城服務(wù)會(huì)經(jīng)過如下業(yè)務(wù)流程治力。

(1)檢測(cè)驗(yàn)證碼是否正確

用戶發(fā)起秒殺請(qǐng)求時(shí),會(huì)將驗(yàn)證碼一同發(fā)送過來勃黍,系統(tǒng)會(huì)檢驗(yàn)驗(yàn)證碼是否有效宵统,并且是否正確。

(2)是否限流

系統(tǒng)會(huì)對(duì)用戶的請(qǐng)求進(jìn)行是否限流的判斷覆获,這里马澈,我們可以通過判斷消息隊(duì)列的長度來進(jìn)行判斷。因?yàn)槲覀儗⒂脩舻恼?qǐng)求放在了消息隊(duì)列中弄息,消息隊(duì)列中堆積的是用戶的請(qǐng)求痊班,我們可以根據(jù)當(dāng)前消息隊(duì)列中存在的待處理的請(qǐng)求數(shù)量來判斷是否需要對(duì)用戶的請(qǐng)求進(jìn)行限流處理。

例如摹量,在秒殺活動(dòng)中涤伐,我們出售1000件商品馒胆,此時(shí)在消息隊(duì)列中存在1000個(gè)請(qǐng)求凝果,如果后續(xù)仍然有用戶發(fā)起秒殺請(qǐng)求豆村,則后續(xù)的請(qǐng)求我們可以不再處理,直接向用戶返回商品已售完的提示四啰。

所以柑晒,使用限流后匙赞,我們可以更快的處理用戶的請(qǐng)求和釋放連接的資源妖碉。

(3)發(fā)送MQ

用戶的秒殺請(qǐng)求通過前面的驗(yàn)證后欧宜,我們就可以將用戶的請(qǐng)求參數(shù)等信息發(fā)送到MQ中進(jìn)行異步處理,同時(shí)席镀,向用戶響應(yīng)結(jié)果信息夏漱。在商城服務(wù)中挂绰,會(huì)有專門的異步任務(wù)處理模塊來消費(fèi)消息隊(duì)列中的請(qǐng)求扮授,并處理后續(xù)的異步流程。

在用戶發(fā)起秒殺請(qǐng)求時(shí),異步下單流程比同步下單流程處理的業(yè)務(wù)操作更少荔仁,它將后續(xù)的操作通過MQ發(fā)送給異步處理模塊進(jìn)行處理乏梁,并迅速向用戶返回響應(yīng)結(jié)果,釋放請(qǐng)求連接卖毁。

2.異步處理

我們可以將下單流程的如下操作進(jìn)行異步處理亥啦。

(1)判斷活動(dòng)是否已經(jīng)結(jié)束

(2)判斷本次請(qǐng)求是否處于系統(tǒng)黑名單翔脱,為了防止電商領(lǐng)域同行的惡意競(jìng)爭(zhēng)可以為系統(tǒng)增加黑名單機(jī)制届吁,將惡意的請(qǐng)求放入系統(tǒng)的黑名單中绿鸣〕蹦#可以使用攔截器統(tǒng)計(jì)訪問頻次來實(shí)現(xiàn)再登。

(3)扣減緩存中的秒殺商品的庫存數(shù)量锉矢。

(4)生成秒殺Token,這個(gè)Token是綁定當(dāng)前用戶和當(dāng)前秒殺活動(dòng)的灯节,只有生成了秒殺Token的請(qǐng)求才有資格進(jìn)行秒殺活動(dòng)炎疆。

這里我們引入了異步處理機(jī)制国裳,在異步處理中缝左,系統(tǒng)使用多少資源,分配多少線程來處理相應(yīng)的任務(wù)挪钓,是可以進(jìn)行控制的碌上。

3.短輪詢查詢秒殺結(jié)果

這里浦徊,可以采取客戶端短輪詢查詢是否獲得秒殺資格的方案辑畦。例如纯出,客戶端可以每隔3秒鐘輪詢請(qǐng)求服務(wù)器暂筝,查詢是否獲得秒殺資格焕襟,這里鸵赖,我們?cè)诜?wù)器的處理就是判斷當(dāng)前用戶是否存在秒殺Token它褪,如果服務(wù)器為當(dāng)前用戶生成了秒殺Token茫打,則當(dāng)前用戶存在秒殺資格老赤。否則繼續(xù)輪詢查詢抬旺,直到超時(shí)或者服務(wù)器返回商品已售完或者無秒殺資格等信息為止嚷狞。

采用短輪詢查詢秒殺結(jié)果時(shí)荣堰,在頁面上我們同樣可以提示用戶排隊(duì)處理中薇搁,但是此時(shí)客戶端會(huì)每隔幾秒輪詢服務(wù)器查詢秒殺資格的狀態(tài)啃洋,相比于同步下單流程來說宏娄,無需長時(shí)間占用請(qǐng)求連接孵坚。

此時(shí)卖宠,可能會(huì)有網(wǎng)友會(huì)問:采用短輪詢查詢的方式筷畦,會(huì)不會(huì)存在直到超時(shí)也查詢不到是否具有秒殺資格的狀態(tài)呢鳖宾?答案是:有可能! 這里我們?cè)囅胍幌旅霘⒌恼鎸?shí)場(chǎng)景漂问,商家參加秒殺活動(dòng)本質(zhì)上不是為了賺錢蚤假,而是提升商品的銷量和商家的知名度,吸引更多的用戶來買自己的商品灶平。所以罐监,我們不必保證用戶能夠100%的查詢到是否具有秒殺資格的狀態(tài)弓柱。

4.秒殺結(jié)算

(1)驗(yàn)證下單Token

客戶端提交秒殺結(jié)算時(shí),會(huì)將秒殺Token一同提交到服務(wù)器,商城服務(wù)會(huì)驗(yàn)證當(dāng)前的秒殺Token是否有效酿箭。

(2)加入秒殺購物車

商城服務(wù)在驗(yàn)證秒殺Token合法并有效后,會(huì)將用戶秒殺的商品添加到秒殺購物車。

5.提交訂單

(1)訂單入庫

將用戶提交的訂單信息保存到數(shù)據(jù)庫中。

(2)刪除Token

秒殺商品訂單入庫成功后,刪除秒殺Token挣磨。

這里大家可以思考一個(gè)問題:我們?yōu)槭裁粗辉诋惒较聠瘟鞒痰姆凵糠植捎卯惒教幚砘缍鴽]有在其他部分采取異步削峰和填谷的措施呢茁裙?

這是因?yàn)樵诋惒较聠瘟鞒痰脑O(shè)計(jì)中,無論是在產(chǎn)品設(shè)計(jì)上還是在接口設(shè)計(jì)上节仿,我們?cè)谟脩舭l(fā)起秒殺請(qǐng)求階段對(duì)用戶的請(qǐng)求進(jìn)行了限流操作晤锥,可以說,系統(tǒng)的限流操作是非常前置的。在用戶發(fā)起秒殺請(qǐng)求時(shí)進(jìn)行了限流矾瘾,系統(tǒng)的高峰流量已經(jīng)被平滑解決了女轿,再往后走,其實(shí)系統(tǒng)的并發(fā)量和系統(tǒng)流量并不是非常高了壕翩。

所以蛉迹,網(wǎng)上很多的文章和帖子中在介紹秒殺系統(tǒng)時(shí),說是在下單時(shí)使用異步削峰來進(jìn)行一些限流操作戈泼,那都是在扯淡婿禽! 因?yàn)橄聠尾僮髟谡麄€(gè)秒殺系統(tǒng)的流程中屬于比較靠后的操作了赏僧,限流操作一定要前置處理大猛,在秒殺業(yè)務(wù)后面的流程中做限流操作是沒啥卵用的。

高并發(fā)“黑科技”與致勝奇招

假設(shè)淀零,在秒殺系統(tǒng)中我們使用Redis實(shí)現(xiàn)緩存挽绩,假設(shè)Redis的讀寫并發(fā)量在5萬左右。我們的商城秒殺業(yè)務(wù)需要支持的并發(fā)量在100萬左右驾中。如果這100萬的并發(fā)全部打入Redis中唉堪,Redis很可能就會(huì)掛掉,那么肩民,我們?nèi)绾谓鉀Q這個(gè)問題呢唠亚?接下來,我們就一起來探討這個(gè)問題持痰。

在高并發(fā)的秒殺系統(tǒng)中灶搜,如果采用Redis緩存數(shù)據(jù),則Redis緩存的并發(fā)處理能力是關(guān)鍵工窍,因?yàn)楹芏嗟那熬Y操作都需要訪問Redis割卖。而異步削峰只是基本的操作,關(guān)鍵還是要保證Redis的并發(fā)處理能力患雏。

解決這個(gè)問題的關(guān)鍵思想就是:分而治之鹏溯,將商品庫存分開放。

暗度陳倉

我們?cè)赗edis中存儲(chǔ)秒殺商品的庫存數(shù)量時(shí)淹仑,可以將秒殺商品的庫存進(jìn)行“分割”存儲(chǔ)來提升Redis的讀寫并發(fā)量丙挽。

例如,原來的秒殺商品的id為10001匀借,庫存為1000件颜阐,在Redis中的存儲(chǔ)為(10001, 1000),我們將原有的庫存分割為5份怀吻,則每份的庫存為200件瞬浓,此時(shí),我們?cè)赗edia中存儲(chǔ)的信息為(10001_0, 200)蓬坡,(10001_1, 200)猿棉,(10001_2, 200)磅叛,(10001_3, 200),(10001_4, 200)萨赁。

此時(shí)弊琴,我們將庫存進(jìn)行分割后,每個(gè)分割后的庫存使用商品id加上一個(gè)數(shù)字標(biāo)識(shí)來存儲(chǔ)杖爽,這樣敲董,在對(duì)存儲(chǔ)商品庫存的每個(gè)Key進(jìn)行Hash運(yùn)算時(shí),得出的Hash結(jié)果是不同的慰安,這就說明腋寨,存儲(chǔ)商品庫存的Key有很大概率不在Redis的同一個(gè)槽位中,這就能夠提升Redis處理請(qǐng)求的性能和并發(fā)量化焕。

分割庫存后萄窜,我們還需要在Redis中存儲(chǔ)一份商品id和分割庫存后的Key的映射關(guān)系,此時(shí)映射關(guān)系的Key為商品的id撒桨,也就是10001查刻,Value為分割庫存后存儲(chǔ)庫存信息的Key,也就是10001_0凤类,10001_1穗泵,10001_2,10001_3谜疤,10001_4佃延。在Redis中我們可以使用List來存儲(chǔ)這些值。

在真正處理庫存信息時(shí)茎截,我們可以先從Redis中查詢出秒殺商品對(duì)應(yīng)的分割庫存后的所有Key苇侵,同時(shí)使用AtomicLong來記錄當(dāng)前的請(qǐng)求數(shù)量,使用請(qǐng)求數(shù)量對(duì)從Redia中查詢出的秒殺商品對(duì)應(yīng)的分割庫存后的所有Key的長度進(jìn)行求模運(yùn)算企锌,得出的結(jié)果為0榆浓,1,2撕攒,3陡鹃,4。再在前面拼接上商品id就可以得出真正的庫存緩存的Key抖坪。此時(shí)萍鲸,就可以根據(jù)這個(gè)Key直接到Redis中獲取相應(yīng)的庫存信息。

移花接木

在高并發(fā)業(yè)務(wù)場(chǎng)景中擦俐,我們可以直接使用Lua腳本庫(OpenResty)從負(fù)載均衡層直接訪問緩存脊阴。

這里,我們思考一個(gè)場(chǎng)景:如果在秒殺業(yè)務(wù)場(chǎng)景中,秒殺的商品被瞬間搶購一空嘿期。此時(shí)品擎,用戶再發(fā)起秒殺請(qǐng)求時(shí),如果系統(tǒng)由負(fù)載均衡層請(qǐng)求應(yīng)用層的各個(gè)服務(wù)备徐,再由應(yīng)用層的各個(gè)服務(wù)訪問緩存和數(shù)據(jù)庫萄传,其實(shí),本質(zhì)上已經(jīng)沒有任何意義了蜜猾,因?yàn)樯唐芬呀?jīng)賣完了秀菱,再通過系統(tǒng)的應(yīng)用層進(jìn)行層層校驗(yàn)已經(jīng)沒有太多意義了!蹭睡!而應(yīng)用層的并發(fā)訪問量是以百為單位的衍菱,這又在一定程度上會(huì)降低系統(tǒng)的并發(fā)度。

為了解決這個(gè)問題棠笑,此時(shí)梦碗,我們可以在系統(tǒng)的負(fù)載均衡層取出用戶發(fā)送請(qǐng)求時(shí)攜帶的用戶id,商品id和秒殺活動(dòng)id等信息蓖救,直接通過Lua腳本等技術(shù)來訪問緩存中的庫存信息。如果秒殺商品的庫存小于或者等于0印屁,則直接返回用戶商品已售完的提示信息循捺,而不用再經(jīng)過應(yīng)用層的層層校驗(yàn)了。 針對(duì)這個(gè)架構(gòu)雄人,我們可以參見本文中的電商系統(tǒng)的架構(gòu)圖(正文開始的第一張圖)从橘。

Redis助力秒殺系統(tǒng)

我們可以在Redis中設(shè)計(jì)一個(gè)Hash數(shù)據(jù)結(jié)構(gòu),來支持商品庫存的扣減操作础钠,如下所示恰力。

seckill:goodsStock:${goodsId}{
    totalCount:200,
    initStatus:0,
    seckillCount:0
}

在我們?cè)O(shè)計(jì)的Hash數(shù)據(jù)結(jié)構(gòu)中,有三個(gè)非常主要的屬性旗吁。

  • totalCount:表示參與秒殺的商品的總數(shù)量踩萎,在秒殺活動(dòng)開始前,我們就需要提前將此值加載到Redis緩存中很钓。
  • initStatus:我們把這個(gè)值設(shè)計(jì)成一個(gè)布爾值香府。秒殺開始前,這個(gè)值為0码倦,表示秒殺未開始企孩。可以通過定時(shí)任務(wù)或者后臺(tái)操作袁稽,將此值修改為1勿璃,則表示秒殺開始。
  • seckillCount:表示秒殺的商品數(shù)量,在秒殺過程中补疑,此值的上限為totalCount闻葵,當(dāng)此值達(dá)到totalCount時(shí),表示商品已經(jīng)秒殺完畢癣丧。

我們可以通過下面的代碼片段在秒殺預(yù)熱階段槽畔,將要參與秒殺的商品數(shù)據(jù)加載的緩存。

/**
 * @author binghe
 * @description 秒殺前構(gòu)建商品緩存代碼示例
 */
public class SeckillCacheBuilder{
    private static final String GOODS_CACHE = "seckill:goodsStock:"; 
    private String getCacheKey(String id) { 
        return  GOODS_CACHE.concat(id);
    } 
    public void prepare(String id, int totalCount) { 
        String key = getCacheKey(id); 
        Map<String, Integer> goods = new HashMap<>(); 
        goods.put("totalCount", totalCount); 
        goods.put("initStatus", 0); 
        goods.put("seckillCount", 0); 
        redisTemplate.opsForHash().putAll(key, goods); 
     }
}

秒殺開始的時(shí)候胁编,我們需要在代碼中首先判斷緩存中的seckillCount值是否小于totalCount值厢钧,如果seckillCount值確實(shí)小于totalCount值,我們才能夠?qū)齑孢M(jìn)行鎖定嬉橙。在我們的程序中早直,這兩步其實(shí)并不是原子性的。如果在分布式環(huán)境中市框,我們通過多臺(tái)機(jī)器同時(shí)操作Redis緩存霞扬,就會(huì)發(fā)生同步問題,進(jìn)而引起“超賣”的嚴(yán)重后果枫振。

在電商領(lǐng)域扮惦,有一個(gè)專業(yè)名詞叫作“超賣”。顧名思義:“超賣”就是說賣出的商品數(shù)量比商品的庫存數(shù)量多伊磺,這在電商領(lǐng)域是一個(gè)非常嚴(yán)重的問題蜀备。那么,我們?nèi)绾谓鉀Q“超賣”問題呢杖小?

Lua腳本完美解決超賣問題

我們?nèi)绾谓鉀Q多臺(tái)機(jī)器同時(shí)操作Redis出現(xiàn)的同步問題呢肆汹?一個(gè)比較好的方案就是使用Lua腳本。我們可以使用Lua腳本將Redis中扣減庫存的操作封裝成一個(gè)原子操作予权,這樣就能夠保證操作的原子性昂勉,從而解決高并發(fā)環(huán)境下的同步問題。

例如扫腺,我們可以編寫如下的Lua腳本代碼岗照,來執(zhí)行Redis中的庫存扣減操作。

local resultFlag = "0" 
local n = tonumber(ARGV[1]) 
local key = KEYS[1] 
local goodsInfo = redis.call("HMGET",key,"totalCount","seckillCount") 
local total = tonumber(goodsInfo[1]) 
local alloc = tonumber(goodsInfo[2]) 
if not total then 
    return resultFlag 
end 
if total >= alloc + n  then 
    local ret = redis.call("HINCRBY",key,"seckillCount",n) 
    return tostring(ret) 
end 
return resultFlag

我們可以使用如下的Java代碼來調(diào)用上述Lua腳本斧账。

public int secKill(String id, int number) { 
    String key = getCacheKey(id); 
    Object seckillCount =  redisTemplate.execute(script, Arrays.asList(key), String.valueOf(number)); 
    return Integer.valueOf(seckillCount.toString()); 
}

這樣谴返,我們?cè)趫?zhí)行秒殺活動(dòng)時(shí),就能夠保證操作的原子性咧织,從而有效的避免數(shù)據(jù)的同步問題嗓袱,進(jìn)而有效的解決了“超賣”問題。

好了习绢,今天我們就到這兒吧渠抹,我是冰河蝙昙,我們下期見!梧却!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末奇颠,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子放航,更是在濱河造成了極大的恐慌烈拒,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,000評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件广鳍,死亡現(xiàn)場(chǎng)離奇詭異荆几,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)赊时,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門吨铸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人祖秒,你說我怎么就攤上這事诞吱。” “怎么了竭缝?”我有些...
    開封第一講書人閱讀 168,561評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵房维,是天一觀的道長。 經(jīng)常有香客問我歌馍,道長握巢,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,782評(píng)論 1 298
  • 正文 為了忘掉前任松却,我火速辦了婚禮,結(jié)果婚禮上溅话,老公的妹妹穿的比我還像新娘晓锻。我一直安慰自己,他們只是感情好飞几,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評(píng)論 6 397
  • 文/花漫 我一把揭開白布砚哆。 她就那樣靜靜地躺著,像睡著了一般屑墨。 火紅的嫁衣襯著肌膚如雪躁锁。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,394評(píng)論 1 310
  • 那天卵史,我揣著相機(jī)與錄音战转,去河邊找鬼。 笑死以躯,一個(gè)胖子當(dāng)著我的面吹牛槐秧,可吹牛的內(nèi)容都是我干的啄踊。 我是一名探鬼主播,決...
    沈念sama閱讀 40,952評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼刁标,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼颠通!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起膀懈,我...
    開封第一講書人閱讀 39,852評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤顿锰,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后启搂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體硼控,經(jīng)...
    沈念sama閱讀 46,409評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,483評(píng)論 3 341
  • 正文 我和宋清朗相戀三年狐血,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了淀歇。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,615評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡匈织,死狀恐怖浪默,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情缀匕,我是刑警寧澤纳决,帶...
    沈念sama閱讀 36,303評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站乡小,受9級(jí)特大地震影響阔加,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜满钟,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,979評(píng)論 3 334
  • 文/蒙蒙 一胜榔、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧湃番,春花似錦夭织、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至泥兰,卻和暖如春弄屡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背鞋诗。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評(píng)論 1 272
  • 我被黑心中介騙來泰國打工膀捷, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人师脂。 一個(gè)月前我還...
    沈念sama閱讀 49,041評(píng)論 3 377
  • 正文 我出身青樓担孔,卻偏偏與公主長得像江锨,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子糕篇,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,630評(píng)論 2 359