高并發(fā)秒殺架構(gòu)模型設(shè)計附源碼案例

前言

秒殺系統(tǒng)相信很多人見過赎败,比如京東或者淘寶的秒殺歪玲,小米手機的秒殺迁央。

那么秒殺系統(tǒng)的后臺是如何實現(xiàn)的呢?我們?nèi)绾卧O(shè)計一個秒殺系統(tǒng)呢滥崩?對于秒殺系統(tǒng)應該考慮哪些問題岖圈?如何設(shè)計出健壯的秒殺系統(tǒng)?本期我們就來探討一下這個問題:


一:秒殺應該考慮哪些問題

1.1:超賣問題

分析秒殺的業(yè)務場景钙皮,最重要的有一點就是超賣問題蜂科,假如備貨只有100個顽决,但是最終超賣了200,一般來講秒殺系統(tǒng)的價格都比較低导匣,如果超賣將嚴重影響公司的財產(chǎn)利益擎值,因此首當其沖的就是解決商品的超賣問題。

1.2:高并發(fā)

秒殺具有時間短逐抑、并發(fā)量大的特點鸠儿,秒殺持續(xù)時間只有幾分鐘,而一般公司都為了制造轟動效應厕氨,會以極低的價格來吸引用戶进每,因此參與搶購的用戶會非常的多。

短時間內(nèi)會有大量請求涌進來命斧,后端如何防止并發(fā)過高造成緩存擊穿或者失效田晚,擊垮數(shù)據(jù)庫都是需要考慮的問題。

1.3:接口防刷

現(xiàn)在的秒殺大多都會出來針對秒殺對應的軟件国葬,這類軟件會模擬不斷向后臺服務器發(fā)起請求贤徒,一秒幾百次都是很常見的,如何防止這類軟件的重復無效請求汇四,防止不斷發(fā)起的請求也是需要我們針對性考慮的

1.4:秒殺url

對于普通用戶來講接奈,看到的只是一個比較簡單的秒殺頁面,在未達到規(guī)定時間通孽,秒殺按鈕是灰色的序宦,一旦到達規(guī)定時間,灰色按鈕變成可點擊狀態(tài)背苦。這部分是針對小白用戶的

如果是稍微有點電腦功底的用戶互捌,會通過F12看瀏覽器的network看到秒殺的url,通過特定軟件去請求也可以實現(xiàn)秒殺行剂。

或者提前知道秒殺url的人秕噪,一請求就直接實現(xiàn)秒殺了。這個問題我們需要考慮解決厚宰。

1.5:數(shù)據(jù)庫設(shè)計

秒殺有把我們服務器擊垮的風險腌巾,如果讓它與我們的其他業(yè)務使用在同一個數(shù)據(jù)庫中,耦合在一起固阁,就很有可能牽連和影響其他的業(yè)務壤躲。

如何防止這類問題發(fā)生,就算秒殺發(fā)生了宕機备燃、服務器卡死問題碉克,也應該讓他盡量不影響線上正常進行的業(yè)務。

1.6:大量請求問題

按照1.2的考慮并齐,就算使用緩存還是不足以應對短時間的高并發(fā)的流量的沖擊漏麦。如何承載這樣巨大的訪問量客税,同時提供穩(wěn)定低時延的服務保證,是需要面對的一大挑戰(zhàn)撕贞。

我們來算一筆賬更耻,假如使用的是redis緩存,單臺redis服務器可承受的QPS大概是4W左右捏膨,如果一個秒殺吸引的用戶量足夠多的話秧均,單QPS可能達到幾十萬,單體redis還是不足以支撐如此巨大的請求量号涯。緩存會被擊穿目胡,直接滲透到DB,從而擊垮mysql链快。后臺會將會大量報錯誉己。

二:秒殺系統(tǒng)的設(shè)計和技術(shù)方案

2.1:秒殺系統(tǒng)數(shù)據(jù)庫設(shè)計

針對1.5提出的秒殺數(shù)據(jù)庫的問題,因此應該單獨設(shè)計一個秒殺數(shù)據(jù)庫域蜗,防止因為秒殺活動的高并發(fā)訪問拖垮整個網(wǎng)站巨双。

這里只需要兩張表,一張是秒殺訂單表霉祸,一張是秒殺貨品表

其實應該還有幾張表筑累,商品表:可以關(guān)聯(lián)goods_id查到具體的商品信息,商品圖像脉执、名稱疼阔、平時價格、秒殺價格等半夷,還有用戶表:根據(jù)用戶user_id可以查詢到用戶昵稱、用戶手機號迅细,收貨地址等其他額外信息巫橄,這個具體就不給出實例了。

2.2:秒殺url的設(shè)計

為了避免有程序訪問經(jīng)驗的人通過下單頁面url直接訪問后臺接口來秒殺貨品茵典,我們需要將秒殺的url實現(xiàn)動態(tài)化湘换,即使是開發(fā)整個系統(tǒng)的人都無法在秒殺開始前知道秒殺的url。

具體的做法就是通過md5加密一串隨機字符作為秒殺的url统阿,然后前端訪問后臺獲取具體的url彩倚,后臺校驗通過之后才可以繼續(xù)秒殺。

2.3:秒殺頁面靜態(tài)化

將商品的描述扶平、參數(shù)帆离、成交記錄、圖像结澄、評價等全部寫入到一個靜態(tài)頁面哥谷,用戶請求不需要通過訪問后端服務器岸夯,不需要經(jīng)過數(shù)據(jù)庫,直接在前臺客戶端生成们妥,這樣可以最大可能的減少服務器的壓力猜扮。

具體的方法可以使用freemarker模板技術(shù),建立網(wǎng)頁模板监婶,填充數(shù)據(jù)旅赢,然后渲染網(wǎng)頁。

2.4:單體redis升級為集群redis

秒殺是一個讀多寫少的場景惑惶,使用redis做緩存再合適不過鲜漩。不過考慮到緩存擊穿問題,我們應該構(gòu)建redis集群集惋,采用哨兵模式孕似,可以提升redis的性能和可用性。

2.5:使用nginx

nginx是一個高性能web服務器刮刑,它的并發(fā)能力可以達到幾萬喉祭,而tomcat只有幾百。通過nginx映射客戶端請求雷绢,再分發(fā)到后臺tomcat服務器集群中可以大大提升并發(fā)能力泛烙。

2.6:精簡sql

典型的一個場景是在進行扣減庫存的時候,傳統(tǒng)的做法是先查詢庫存翘紊,再去update蔽氨。這樣的話需要兩個sql,而實際上一個sql我們就可以完成的帆疟。

可以用這樣的做法:

update miaosha_goods set stock =stock-1 where goos_id ={#goods_id} and version = #{version} and sock>0;

這樣的話鹉究,就可以保證庫存不會超賣并且一次更新庫存,還有注意一點這里使用了版本號的樂觀鎖,相比較悲觀鎖踪宠,它的性能較好自赔。

2.7:redis預減庫存

很多請求進來,都需要后臺查詢庫存,這是一個頻繁讀的場景柳琢∩芊粒可以使用redis來預減庫存,在秒殺開始前可以在redis設(shè)置

比如 redis.set(goodsId,100)柬脸,這里預放的庫存為100可以設(shè)值為常量),每次下單成功之后他去,Integer stock = (Integer)redis.get(goosId); 然后判斷sock的值,如果小于常量值就減去1倒堕。

不過注意當取消的時候灾测,需要增加庫存,增加庫存的時候也得注意不能大于之間設(shè)定的總庫存數(shù)(查詢庫存和扣減庫存需要原子操作涩馆,此時可以借助lua腳本)下次下單再獲取庫存的時候行施,直接從redis里面查就可以了允坚。

2.8:接口限流

秒殺最終的本質(zhì)是數(shù)據(jù)庫的更新,但是有很多大量無效的請求蛾号,我們最終要做的就是如何把這些無效的請求過濾掉稠项,防止?jié)B透到數(shù)據(jù)庫。

限流的話鲜结,需要入手的方面很多:

2.8.1:前端限流

首先第一步就是通過前端限流展运,用戶在秒殺按鈕點擊以后發(fā)起請求,那么在接下來的5秒是無法點擊(通過設(shè)置按鈕為disable)精刷。這一小舉措開發(fā)起來成本很小拗胜,但是很有效。

2.8.2:同一個用戶xx秒內(nèi)重復請求直接拒絕

具體多少秒需要根據(jù)實際業(yè)務和秒殺的人數(shù)而定怒允,一般限定為10秒埂软。

具體的做法就是通過redis的鍵過期策略,首先對每個請求都從String value = redis.get(userId);

如果獲取到這個value為空或者為null纫事,表示它是有效的請求勘畔,然后放行這個請求。如果不為空表示它是重復性請求丽惶,直接丟掉這個請求炫七。

如果有效,采用redis.setexpire(userId,value,10).value可以是任意值钾唬,一般放業(yè)務屬性比較好万哪,這個是設(shè)置以userId為key,10秒的過期時間(10秒后,key對應的值自動為null)

2.8.3:令牌桶算法限流

接口限流的策略有很多抡秆,我們這里采用令牌桶算法奕巍。

令牌桶算法的基本思路是每個請求嘗試獲取一個令牌,后端只處理持有令牌的請求琅轧,生產(chǎn)令牌的速度和效率我們都可以自己限定伍绳,guava提供了RateLimter的api供我們使用。

以下做一個簡單的例子乍桂,注意需要引入guava

上面代碼的思路就是通過RateLimiter來限定我們的令牌桶每秒產(chǎn)生1個令牌(生產(chǎn)的效率比較低),循環(huán)10次去執(zhí)行任務效床。

acquire會阻塞當前線程直到獲取到令牌睹酌,也就是如果任務沒有獲取到令牌,會一直等待剩檀。那么請求就會卡在我們限定的時間內(nèi)才可以繼續(xù)往下走憋沿,這個方法返回的是線程具體等待的時間。

執(zhí)行如下:

可以看到任務執(zhí)行的過程中沪猴,第1個是無需等待的辐啄,因為已經(jīng)在開始的第1秒生產(chǎn)出了令牌采章。

接下來的任務請求就必須等到令牌桶產(chǎn)生了令牌才可以繼續(xù)往下執(zhí)行。如果沒有獲取到就會阻塞(有一個停頓的過程)壶辜。

不過這個方式不太好悯舟,因為用戶如果在客戶端請求,如果較多的話砸民,直接后臺在生產(chǎn)token就會卡頓(用戶體驗較差)抵怎,它是不會拋棄任務的,我們需要一個更優(yōu)秀的策略:如果超過某個時間沒有獲取到岭参,直接拒絕該任務反惕。

接下來再來個案例:

其中用到了tryAcquire方法,這個方法的主要作用是設(shè)定一個超時的時間演侯,如果在指定的時間內(nèi)預估(注意是預估并不會真實的等待)姿染,如果能拿到令牌就返回true,如果拿不到就返回false秒际。

然后我們讓無效的直接跳過悬赏,這里設(shè)定每秒生產(chǎn)1個令牌,讓每個任務嘗試在0.5秒獲取令牌程癌,如果獲取不到,就直接跳過這個任務(放在秒殺環(huán)境里就是直接拋棄這個請求)舷嗡;

程序?qū)嶋H運行如下:

只有第1個獲取到了令牌,順利執(zhí)行了嵌莉,下面的基本都直接拋棄了进萄,因為0.5秒內(nèi),令牌桶(1秒1個)來不及生產(chǎn)就肯定獲取不到返回false了锐峭。

這個限流策略的效率有多高呢中鼠?假如我們的并發(fā)請求是400萬瞬間的請求,將令牌產(chǎn)生的效率設(shè)為每秒20個沿癞,每次嘗試獲取令牌的時間是0.05秒援雇,那么最終測試下來的結(jié)果是,每次只會放行4個左右的請求椎扬,大量的請求會被拒絕惫搏,這就是令牌桶算法的優(yōu)秀之處。

2.9:異步下單

為了提升下單的效率蚕涤,并且防止下單服務的失敗筐赔。需要將下單這一操作進行異步處理。

最常采用的辦法是使用隊列揖铜,隊列最顯著的三個優(yōu)點:異步茴丰、削峰、解耦。

這里可以采用rabbitmq贿肩,在后臺經(jīng)過了限流峦椰、庫存校驗之后,流入到這一步驟的就是有效請求汰规。然后發(fā)送到隊列里汤功,隊列接受消息,異步下單控轿。

下完單冤竹,入庫沒有問題可以用短信通知用戶秒殺成功。假如失敗的話,可以采用補償機制茬射,重試鹦蠕。

2.10:服務降級

假如在秒殺過程中出現(xiàn)了某個服務器宕機,或者服務不可用在抛,應該做好后備工作钟病。之前的博客里有介紹通過Hystrix進行服務熔斷和降級,可以開發(fā)一個備用服務刚梭。

假如服務器真的宕機了肠阱,直接給用戶一個友好的提示返回,而不是直接卡死朴读,服務器錯誤等生硬的反饋屹徘。

三:總結(jié)

秒殺流程圖:

這就是我設(shè)計出來的秒殺流程圖,當然不同的秒殺體量針對的技術(shù)選型都不一樣衅金,這個流程可以支撐起幾十萬的流量噪伊,如果是成千萬破億那就得重新設(shè)計了。比如數(shù)據(jù)庫的分庫分表氮唯、隊列改成用kafka鉴吹、redis增加集群數(shù)量等手段。

通過本次設(shè)計主要是要表明的是我們?nèi)绾螒獙Ω卟l(fā)的處理惩琉,并開始嘗試解決它豆励,在工作中多思考、多動手能提升我們的能力水平瞒渠,加油良蒸!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市伍玖,隨后出現(xiàn)的幾起案子诚啃,更是在濱河造成了極大的恐慌,老刑警劉巖私沮,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡仔燕,警方通過查閱死者的電腦和手機造垛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來晰搀,“玉大人五辽,你說我怎么就攤上這事⊥馑。” “怎么了杆逗?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長鳞疲。 經(jīng)常有香客問我罪郊,道長,這世上最難降的妖魔是什么尚洽? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任悔橄,我火速辦了婚禮,結(jié)果婚禮上腺毫,老公的妹妹穿的比我還像新娘癣疟。我一直安慰自己,他們只是感情好潮酒,可當我...
    茶點故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布睛挚。 她就那樣靜靜地躺著,像睡著了一般急黎。 火紅的嫁衣襯著肌膚如雪扎狱。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天叁熔,我揣著相機與錄音委乌,去河邊找鬼。 笑死荣回,一個胖子當著我的面吹牛遭贸,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播心软,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼壕吹,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了删铃?” 一聲冷哼從身側(cè)響起耳贬,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎猎唁,沒想到半個月后咒劲,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年腐魂,在試婚紗的時候發(fā)現(xiàn)自己被綠了帐偎。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡蛔屹,死狀恐怖削樊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情兔毒,我是刑警寧澤漫贞,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布蔗牡,位于F島的核電站第晰,受9級特大地震影響莽囤,放射性物質(zhì)發(fā)生泄漏掷伙。R本人自食惡果不足惜不傅,卻給世界環(huán)境...
    茶點故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一土砂、第九天 我趴在偏房一處隱蔽的房頂上張望咸作。 院中可真熱鬧宪迟,春花似錦昵骤、人聲如沸树碱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽成榜。三九已至,卻和暖如春蹦玫,著一層夾襖步出監(jiān)牢的瞬間赎婚,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工樱溉, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留挣输,地道東北人。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓福贞,卻偏偏與公主長得像撩嚼,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子挖帘,可洞房花燭夜當晚...
    茶點故事閱讀 44,927評論 2 355

推薦閱讀更多精彩內(nèi)容