秒殺系統(tǒng)設(shè)計(jì)~億級(jí)用戶
掃描二維碼
隨時(shí)隨地手機(jī)看文章
個(gè)人從事電商行業(yè)十幾年,經(jīng)歷過大大小小的促銷活動(dòng)和秒殺上百次,每次做秒殺瞬時(shí)訪問量會(huì)翻數(shù)十倍,甚至數(shù)百倍。對(duì)系統(tǒng)架構(gòu)是巨大的考驗(yàn),期間也曾經(jīng)歷過系統(tǒng)宕機(jī),甚至整體雪崩。那么我們?cè)趺丛O(shè)計(jì)秒殺系統(tǒng),才能保證秒殺系統(tǒng)的高性能和穩(wěn)定性,同時(shí)還要保證日常業(yè)務(wù)不受影響呢?
先看看秒殺場(chǎng)景特點(diǎn)。秒殺開始前幾分鐘,大量用戶開始進(jìn)入秒殺商品詳情頁(yè)面,很多人開始頻繁刷新秒殺商品詳情頁(yè),這時(shí)秒殺商品詳情頁(yè)訪問量會(huì)猛增。秒殺開始,大量用戶開始搶購(gòu),這時(shí)創(chuàng)建訂單,扣庫(kù)存壓力會(huì)顯著增大。實(shí)際上,秒殺場(chǎng)景基本都是秒殺參與人多,秒殺成功的人卻寥寥無幾,經(jīng)常是幾十萬人或者更多人搶幾百個(gè)商品庫(kù)存。
那么我們?cè)?jīng)是怎么設(shè)計(jì)秒殺系統(tǒng)的呢?主要涉及以下幾個(gè)方面:
秒殺業(yè)務(wù)流程上的考慮:
由于參加秒殺的商品售賣價(jià)格非常低,基本都是“搶到即賺到”,成功下單后卻不付款的情況非常少。所以我們采用下單減庫(kù)存的方案,下單時(shí)扣減庫(kù)存,然后再進(jìn)行支付。假如真有個(gè)別訂單不付款怎么辦?沒關(guān)系,秒殺好活動(dòng)最主要的目的是吸引流量,個(gè)別訂單不支付對(duì)秒殺活動(dòng)本身影響不大。況且,沒支付剩下的庫(kù)存還可以做為普通商品繼續(xù)售賣。不過要注意對(duì)機(jī)器人和自動(dòng)腳本的防御,后面會(huì)詳細(xì)介紹。
頁(yè)面靜態(tài)化:
“秒殺開始前幾分鐘,大量用戶開始進(jìn)入秒殺商品詳情頁(yè)面,很多人開始頻繁刷新秒殺商品詳情頁(yè),這時(shí)秒殺商品詳情頁(yè)訪問量會(huì)猛增”。如果請(qǐng)求全部打到后端服務(wù),那后端服務(wù)的壓力會(huì)非常大(后端服務(wù)要處理業(yè)務(wù)邏輯,而且還要訪問數(shù)據(jù)庫(kù),吞吐量比較低)。
考慮到秒殺是運(yùn)營(yíng)同學(xué)提前安排的活動(dòng),要秒殺哪些商品、商品價(jià)格等信息在秒殺活動(dòng)開始前已經(jīng)確定下來,所以我們可以把秒殺商品詳情頁(yè)做成靜態(tài)頁(yè)面,把商品詳情、商品價(jià)格等參數(shù)、評(píng)論評(píng)價(jià)等信息全部放在這個(gè)靜態(tài)頁(yè)面里,然后把這個(gè)靜態(tài)頁(yè)面上傳到CDN上預(yù)熱(CDN是內(nèi)容分發(fā)網(wǎng)絡(luò),可以簡(jiǎn)單理解成互聯(lián)網(wǎng)上的巨大的緩存,用于存放靜態(tài)頁(yè)面、圖片、視頻等,可以顯著提高訪問速度),用CDN扛流量,這樣大量的商品詳情頁(yè)的訪問請(qǐng)求就不用訪問自己的網(wǎng)站(源站)。這樣既可以提高訪問速度,也沒有給網(wǎng)站增加壓力,同時(shí)也減少了網(wǎng)站帶寬壓力。
請(qǐng)求攔截:
前端頁(yè)面,相關(guān)按鈕點(diǎn)擊后置灰,防止重復(fù)提交
網(wǎng)關(guān)(zuul,nginx)層,為了避免前端惡意請(qǐng)求,比如一些攻擊腳本,在網(wǎng)關(guān)層要對(duì)下單等接口按userID限流,幾秒鐘只能訪問一次??紤]到秒殺場(chǎng)景參與人多,秒殺成功的人極少,我們可以把絕大部分搶購(gòu)下單請(qǐng)求在網(wǎng)關(guān)層直接拒掉,按秒殺失敗處理。這樣就極大減少了后端服務(wù)的壓力。
假設(shè)秒殺庫(kù)存是200個(gè),我們可以只放行200個(gè)請(qǐng)求到后端服務(wù)。要注意,為了盡量避免庫(kù)存被機(jī)器人和自動(dòng)腳本搶走,200個(gè)請(qǐng)求不能在秒殺開始瞬間同時(shí)放行,可以分段放行,比如秒殺開始后隨機(jī)選取100ms內(nèi)的5個(gè)請(qǐng)求放行(這100ms內(nèi)的其他請(qǐng)求直接拒掉,按秒殺失敗處理),之后每隔100ms放行5個(gè)請(qǐng)求,4秒鐘可以放行完200個(gè)請(qǐng)求。分段放行,除了限制了機(jī)器人和自動(dòng)腳本,把請(qǐng)求分散在各個(gè)時(shí)間段,還進(jìn)一步緩解了后端服務(wù)的壓力。
分段放行總時(shí)間不能太長(zhǎng),假如每100ms放行1個(gè)請(qǐng)求,放行完所有200個(gè)請(qǐng)求需要20秒時(shí)間,這樣用戶就會(huì)明顯感知到下單早的人沒秒殺成功,下單晚的人反而秒殺成功了,用戶體驗(yàn)會(huì)變差。
另外,秒殺過程網(wǎng)關(guān)壓力會(huì)比較大,網(wǎng)關(guān)可以做成集群,多節(jié)點(diǎn)分?jǐn)傇L問壓力。
后端服務(wù)設(shè)計(jì):
如果秒殺庫(kù)存只有200,經(jīng)過網(wǎng)關(guān)攔截,再加上采用分段放行的方式,對(duì)于后端服務(wù)基本沒什么壓力了,日常的后端服務(wù)就完全可以支撐秒殺活動(dòng)了。不用再做更復(fù)雜的設(shè)計(jì)。不過,假如秒殺庫(kù)存有幾萬個(gè),放行的下單請(qǐng)求就有幾萬個(gè),為了用戶體驗(yàn)放行總時(shí)間也不能太長(zhǎng),這時(shí)后端服務(wù)該怎么設(shè)計(jì)呢?
這時(shí)主要壓力就在數(shù)據(jù)庫(kù)了,扣減庫(kù)存壓力,創(chuàng)建訂單壓力。
庫(kù)存可以放到Reids緩存中,來提高扣減庫(kù)存吞吐能力。對(duì)于熱點(diǎn)商品的庫(kù)存可以利用Redis分片存儲(chǔ)。
創(chuàng)建訂單可以走異步消息隊(duì)列。后端服務(wù)接到下單請(qǐng)求,直接放進(jìn)消息隊(duì)列,監(jiān)聽服務(wù)取出消息后,先將訂單信息寫入Redis,每隔100ms或者積攢100條訂單,批量寫入數(shù)據(jù)庫(kù)一次。前端頁(yè)面下單后定時(shí)向后端拉取訂單信息,獲取到訂單信息后跳轉(zhuǎn)到支付頁(yè)面。用這種批量異步寫入數(shù)據(jù)庫(kù)的方式大幅減少了數(shù)據(jù)庫(kù)寫入頻次,從而明顯降低了訂單數(shù)據(jù)庫(kù)寫入壓力。
隔離:
1,業(yè)務(wù)隔離。從業(yè)務(wù)上把秒殺和日常的售賣區(qū)分開來,把秒殺做為營(yíng)銷活動(dòng),要參與秒殺的商品需要提前報(bào)名參加活動(dòng),這樣我們就能提前知道哪些商家哪些商品要參與秒殺,可以根據(jù)提報(bào)的商品提前生成靜態(tài)頁(yè)面并上傳到CDN預(yù)熱,提報(bào)的商品庫(kù)存也需要提前預(yù)熱,可以將商品庫(kù)存在活動(dòng)開始前預(yù)熱到Redis,避免秒殺開始后大量的緩存穿透。
2,部署隔離。秒殺相關(guān)服務(wù)和日常服務(wù)要分組部署,不能因?yàn)槊霘⒊鰡栴}影響日常售賣業(yè)務(wù)??梢陨暾?qǐng)單獨(dú)的秒殺域名,從網(wǎng)絡(luò)入口層就開始分流。網(wǎng)關(guān)也單獨(dú)部署,秒殺走自己?jiǎn)为?dú)的網(wǎng)關(guān),從而避免日常網(wǎng)關(guān)受到影響。秒殺可以復(fù)用訂單,庫(kù)存,支付等日常服務(wù),只是需要一些小的改造(比如下單流程走消息隊(duì)列,批量寫入訂單庫(kù),以及在Redis中扣減庫(kù)存)。
3,數(shù)據(jù)隔離。為了避免秒殺活動(dòng)影響到日常售賣業(yè)務(wù),Redis緩存需要單獨(dú)部署,甚至數(shù)據(jù)庫(kù)也需要單獨(dú)部署!數(shù)據(jù)隔離后,秒殺剩余的庫(kù)存怎么辦?秒殺活動(dòng)結(jié)束后,剩余庫(kù)存可以歸還到日常庫(kù)存繼續(xù)做為普通商品售賣。數(shù)據(jù)隔離后,秒殺訂單和日常訂單不在相同的數(shù)據(jù)庫(kù),之后的訂單查詢?cè)趺凑故??可以在?chuàng)建秒殺訂單后發(fā)消息到消息隊(duì)列,日常訂單服務(wù)采取拉的方式消費(fèi)消息,這時(shí)日常訂單服務(wù)是主動(dòng)方,可以采用線程池的方式,根據(jù)機(jī)器的性能來增加或縮小線程池的大小,控制拉取消息的速度,來控制訂單數(shù)據(jù)庫(kù)的寫入壓力。
網(wǎng)絡(luò):
秒殺前要和網(wǎng)絡(luò)運(yùn)營(yíng)商、CDN服務(wù)商提前申請(qǐng)帶寬。
還有哪些細(xì)節(jié)要考慮:
-
如何避免超賣?如果在redis中扣減庫(kù)存,可以利用decr命令扣減庫(kù)存,decr是原子操作,在分布式環(huán)境下也不會(huì)有并發(fā)問題,decr扣減庫(kù)存后,判斷返回值,如果返回值小于0,扣減庫(kù)存失敗,秒殺也就失敗了;如果在數(shù)據(jù)庫(kù)中扣減庫(kù)存可以在where后面加上庫(kù)存大于0的條件,來避免庫(kù)存被減成負(fù)值。這樣就可以避免超賣情況發(fā)生了。
-
接口防刷,前面已經(jīng)提到過,在網(wǎng)關(guān)層對(duì)下單等接口按userID限流。
-
網(wǎng)關(guān)層除了對(duì)userID做限流外,還要做整體限流。在實(shí)際訪問量超過預(yù)估訪問量時(shí),整體限流可以起到保護(hù)作用,避免系統(tǒng)被壓垮。
-
防止重復(fù)下單,按userID限流已經(jīng)起到了防止重復(fù)下單的作用。假如限制同一個(gè)用戶10分鐘能下一次單,一般情況下10分鐘內(nèi),商品早已經(jīng)被搶光了,用戶也就沒有再次下單的機(jī)會(huì)了。
-
可以結(jié)合風(fēng)控系統(tǒng),在網(wǎng)關(guān)層把羊毛黨等有問題的用戶請(qǐng)求直接拒掉。
-
可以在網(wǎng)關(guān)層上面再加一層防火墻或者高防服務(wù),來防御DDos等分布式網(wǎng)絡(luò)攻擊。
好啦,就分享到這里。
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場(chǎng),如有問題,請(qǐng)聯(lián)系我們,謝謝!





