背景
我之前寫過一個秒殺系統的文章不過有些許瑕疵,所以我準備在之前的基礎上進行二次創作,不過讓我決心二創秒殺系統的原因是我最近面試了很多讀者,動不動就是秒殺系統把我整矇蔽了,我懵的主要是秒殺系統的細節大家都不知道,甚至不知道電商公司一個秒殺系統的組成部分。
我之前在某電商公司就是做電商活動的,所以這樣的場景和很多解決方案我是比較清楚的,那我就從我自身去帶著大家看看一個秒殺的設計細節以及中間各種解決方案的利弊,以下就是我設計的秒殺系統,幾乎涵蓋了市面上所有秒殺的實現細節:
正文首先設計一個系統之前,我們需要先確認我們的業務場景是怎麼樣子的,我就帶著大家一起假設一個場景好吧。
我們現場要賣1000件下面這個嬰兒紙尿褲,然後我們根據以往這樣秒殺活動的資料經驗來看,目測來搶這100件紙尿褲的人足足有10萬人。(南極人打錢!)
你一聽,完了呀,這我們的伺服器哪裡頂得住啊!說真的直接打DB肯定掛,但是別急嘛,有暖男敖丙在,任何系統我們開始設計之前我們都應該去思考會出現哪些問題?這裡我羅列了幾個非常經典的問題:
問題高併發:是的高併發這個是我們想都不用想的一個點,一瞬間這麼多人進來這不是高併發什麼時候是呢?
是吧,秒殺的特點就是這樣時間極短、 瞬間使用者量大。
正常的店鋪營銷都是用極低的價格配合上簡訊、APP的精準推送,吸引特別多的使用者來參與這場秒殺,爽了商家苦了開發呀。
秒殺大家都知道如果真的營銷到位,價格誘人,幾十萬的流量我覺得完全不是問題,那單機的Redis我感覺3-4W的QPS還是能頂得住的,但是再高了就沒辦法了,那這個資料隨便搞個熱銷商品的秒殺可能都不止了。
大量的請求進來,我們需要考慮的點就很多了,快取雪崩,快取擊穿,快取穿透這些我之前提到的點都是有可能發生的,出現問題打掛DB那就很難受了,活動失敗使用者體驗差,活動人氣沒了,最後背鍋的還是開發。
超賣:但凡是個秒殺,都怕超賣,我這裡舉例的只是尿不溼,要是換成100個MacBook Pro,商家的預算經費賣100個可以賺點還可以造勢,結果你寫錯程式多賣出去200個,你不發貨使用者投訴你,平臺封你店,你發貨就血虧,你怎麼辦? (沒事看了敖丙的文章直接不怕)
那最後只能殺個開發祭天解氣了,秒殺的價格本來就低了,基本上都是不怎麼賺錢的,超賣了就恐怖了呀,所以超賣也是很關鍵的一個點。
惡意請求:你這麼低的價格,假如我搶到了,我轉手賣掉我不是血賺?就算我不賣我也不虧啊,那使用者知道,你知道,別的別有用心的人(黑客、黃牛...)肯定也知道的。
那簡單啊,我知道你什麼時候搶,我搞個幾十臺機器搞點指令碼,我也模擬出來十幾萬個人左右的請求,那我是不是意味著我基本上有80%的成功率了。
真實情況可能遠遠不止,因為機器請求的速度比人的手速往往快太多了,在貴州的敖丙我每年回家搶高鐵票都是秒光的,我也不知道有沒有黃牛的功勞,我要Diss你,黃牛。杰倫演唱會門票搶不到,我也Diss你。
Tip:科普下,小道訊息了解到的,黃牛的搶票系統,比國內很多小公司的系統還吊很多,架構設計都是頂級的,我用頂配的服務加上頂配的架構設計,你還想看演唱會?還想回家?
不過不用黃牛我回家都難,我們雲貴川跟我一樣要回家過年的仔太多了555!
連結暴露:前面幾個問題大家可能都很好理解,一看到這個有的小夥伴可能會比較疑惑,啥是連結暴露呀?
相信是個開發同學都對這個畫面一點都不陌生吧,懂點行的仔都可以開啟谷歌的開發者模式,然後看看你的網頁程式碼,有的就有URL,但是我寫VUE的時候是事件觸發然後去呼叫檔案裡面的介面看原始碼看不到,但是我可以點選一下檢視你的請求地址啊,不過你好像可以對按鈕在秒殺前置灰。
不管怎麼樣子都有危險,撇開外面的所有的東西你都擋住了,你賣這個東西實在便宜得過分,有誘惑力,你能保證開發不動心?開發知道地址,在秒殺的時候自己提前請求。。。(開發:怎麼TM又是我)
資料庫:每秒上萬甚至十幾萬的QPS(每秒請求數)直接打到資料庫,基本上都要把庫打掛掉,而且你服務不單單是做秒殺的還涉及其他的業務,你沒做降級、限流、熔斷啥的,別的一起掛,小公司的話可能全站崩潰404。
反正不管你秒殺怎麼掛,你別把別的搞掛了對吧,搞掛了就不是殺一個程式設計師能搞定的。
程式設計師:我TM好難啊!
問題都列出來了,那怎麼設計,怎麼解決這些問題就是接下去要考慮的了,我們對症下藥。
我會從我設計的秒殺系統從上到下去給大家介紹我們正常電商秒殺系統在每一層做了些什麼,每一層存在的問題,難點等。
我們從前端開始:
前端秒殺系統普遍都是商城網頁、H5、APP、小程式這幾項。
在前端這一層其實我們可以做的事情有很多,如果用node去做,甚至能直接處理掉整個秒殺,但是node其實應該屬於後端,所以我不討論node Service了。
資源靜態化:秒殺一般都是特定的商品還有頁面模板,現在一般都是前後端分離的,頁面一般都是不會經過後端的,但是前端也要自己的伺服器啊,那就把能提前放入cdn伺服器的東西都放進去,反正把所有能提升效率的步驟都做一下,減少真正秒殺時候伺服器的壓力。
秒殺連結加鹽:我們上面說了連結要是提前暴露出去可能有人直接訪問url就提前秒殺了,那又有小夥伴要說了我做個時間的校驗就好了呀,那我告訴你,知道連結的地址比起頁面人工點選的還是有很大優勢。
我知道url了,那我通過程式不斷獲取最新的北京時間,可以達到毫秒級別的,我就在00毫秒的時候請求,我敢說絕對比你人工點的成功率大太多了,而且我可以一毫秒傳送N次請求,搞不好你賣100個產品我全拿了。
那這種情況怎麼避免?
簡單,把URL動態化,就連寫程式碼的人都不知道,你就通過MD5之類的摘要演算法加密隨機的字串去做url,然後通過前端程式碼獲取url後臺校驗才能通過。
這個只能防止一部分沒耐心繼續破解下去的黑客,有耐心的人研究出來還是能破解,在電商場景存在很多這樣的羊毛黨,那怎麼做呢?
後面我會說。
限流:限流這裡我覺得應該分為前端限流和後端限流。
物理控制:
這是因為怕大家在時間快到的最後幾秒秒瘋狂請求伺服器,然後還沒到秒殺的時候基本上伺服器就掛了。
你敢說你們秒殺的時候不是這樣的?
前端限流:這個很簡單,一般秒殺不會讓你一直點的,一般都是點選一下或者兩下然後幾秒之後才可以繼續點選,這也是保護伺服器的一種手段。
後端限流:秒殺的時候肯定是涉及到後續的訂單生成和支付等操作,但是都只是成功的幸運兒才會走到那一步,那一旦100個產品賣光了,return了一個false,前端直接秒殺結束,然後你後端也關閉後續無效請求的介入了。
Tip:真正的限流還會有限流元件的加入例如:阿里的Sentinel、Hystrix等。我這裡就不展開了,就說一下物理的限流。
我們賣1000件商品,請求有10W,我們不需要把十萬都放進來,你可以放1W請求進來,然後再進行操作,因為秒殺對於使用者本身就是黑盒的,所以你怎麼做的他們是沒感知的,至於為啥放1W進來,而不是剛好1000,是因為會丟掉一些薅羊毛的使用者,至於怎麼判斷,後面的風控階段我會說。
Nginx:Nginx大家想必都不陌生了吧,這玩意是高效能的web伺服器,併發也隨便頂幾萬不是夢,但是我們的Tomcat只能頂幾百的併發呀,那簡單呀負載均衡嘛,一臺服務幾百,那就多搞點,在秒殺的時候多租點流量機。
Tip:據我所知國內某大廠就是在去年春節活動期間租光了亞洲所有的伺服器,小公司也很喜歡在雙十一期間買流量機來頂住壓力。
這樣一對比是不是覺得你的叢集能頂很多了。
惡意請求攔截也需要用到它,一般單個使用者請求次數太誇張,不像人為的請求在閘道器那一層就得攔截掉了,不然請求多了他搶不搶得到是一回事,伺服器壓力上去了,可能佔用網路頻寬或者把伺服器打崩、快取擊穿等等。
風控我可以明確的告訴大家,前面的所有措施還是攔不住很多羊毛黨,因為他們是專業的團隊,他們可以註冊很多賬號來薅你的羊毛,而且不用機器請求,就用群控,操作幾乎跟真實使用者一模一樣。
那怎麼辦,是不是無解了?
這個時候就需要風控同學的介入了,在請求到達後端之前,風控可以根據賬號行為分析出這個賬號機器人的概率大不大,我現在負責公司的某些特殊系統,每個使用者的行為都是會送到我們大資料團隊進行分析處理,給你打上對應標籤的。
那黑客其實也有辦法:養號
他們去黑市買真實使用者有過很多記錄的賬號,買到了還不閒著,幫他們去購物啥的,讓系統無法識別他們是黑號還是真實使用者的號。
怎麼辦?
通殺!是的沒有辦法,只能通殺了,通殺的意思就是,我們通過風管分析出來這個使用者是真實使用者的概率沒有其他使用者概率大,那就認為他是機器了,丟棄他的請求。
之前的限流我們放進來10000個請求,但是我們真正的庫存只有1000個,那我們就算出最有可能是真實使用者的1000人進行秒殺,丟棄其他請求,因為秒殺本來就是黑盒操作的,使用者層面是無感知的,這樣設計能讓真實的使用者買到東西,還可以減少自己被薅羊毛的概率。
風控可以說是流量進入的最後一道門檻了,所以很多公司的風控是很強的,螞蟻金服的風控大家如果了解過就知道了,你的資金在支付寶被盜了,他們是能做到全款補償是有原因的。
後端服務單一職責:設計個能抗住高併發的系統,我覺得還是得單一職責。
什麼意思呢,大家都知道現在設計都是微服務的設計思想,然後再用分散式的部署方式。
也就是我們下單是有個訂單服務,使用者登入管理等有個使用者服務等等,那為啥我們不給秒殺也開個服務,我們把秒殺的程式碼業務邏輯放一起。
單一職責的好處就是就算秒殺沒抗住,秒殺庫崩了,服務掛了,也不會影響到其他的服務。(高可用)
Redis叢集:之前不是說單機的Redis頂不住嘛,那簡單多找幾個兄弟啊,秒殺本來就是讀多寫少,那你們是不是瞬間想起來我之前跟你們提到過的,Redis叢集,主從同步、讀寫分離,我們還搞點哨兵,開啟持久化直接無敵高可用!
庫存預熱:秒殺的本質,就是對庫存的搶奪,每個秒殺的使用者來你都去資料庫查詢庫存校驗庫存,然後扣減庫存,撇開效能因數,你不覺得這樣好繁瑣,對業務開發人員都不友好,而且資料庫頂不住啊。
開發:你tm總算為我著想一次了。
那怎麼辦?我們都知道資料庫頂不住但是他的兄弟非關係型的資料庫Redis能頂啊!
那不簡單了,我們要開始秒殺前你通過定時任務或者運維同學提前把商品的庫存載入到Redis中去,讓整個流程都在Redis裡面去做,然後等秒殺介紹了,再非同步的去修改庫存就好了。
但是用了Redis就有一個問題了,我們上面說了我們採用主從,就是我們會去讀取庫存然後再判斷然後有庫存才去減庫存,正常情況沒問題,但是高併發的情況問題就很大了。
**多品幾遍!!!**就比如現在庫存只剩下1個了,我們高併發嘛,4個伺服器一起查詢了發現都是還有1個,那大家都覺得是自己搶到了,就都去扣庫存,那結果就變成了-3,是的只有一個是真的搶到了,別的都是超賣的。咋辦?
事務:Redis本身是支援事務的,而且他有很多原子命令的,大家也可以用LUA,還可以用他的管道,樂觀鎖他也知支援。
限流&降級&熔斷&隔離:這個為啥要做呢,不怕一萬就怕萬一,萬一你真的頂不住了,限流,頂不住就擋一部分出去但是不能說不行,降級,降級了還是被打掛了,熔斷,至少不要影響別的系統,隔離,你本身就獨立的,但是你會呼叫其他的系統嘛,你快不行了你別拖累兄弟們啊。
訊息佇列(削峰填谷):一說到這個名詞,很多小夥伴就知道了,對的MQ,你買東西少了你直接100個請求改庫我覺得沒問題,但是萬一秒殺一萬個,10萬個呢?伺服器掛了,程式設計師又要背鍋的。
秒殺就是這種瞬間流量很高,但是平時又沒有流量的場景,那訊息佇列完全契合這樣的場景了呀,削峰填谷。
Tip:可能小夥伴說我們業務達不到這個量級,沒必要。但是我想說我們寫程式碼,就不應該寫出有邏輯漏洞的程式碼,至少以後公司體量上去了,別人一看居然不用改程式碼,一看程式碼作者是敖丙?有點東西!
你可以把它放訊息佇列,然後一點點消費去改庫存就好了嘛,不過單個商品其實一次修改就夠了,我這裡說的是某個點多個商品一起秒殺的場景,像極了雙十一零點。
資料庫資料庫用MySQL只要連線池設定合理一般問題是不大的,不過一般大公司不缺錢而且秒殺這樣的活動十分頻繁,我之前所在的公司就是這樣秒殺特賣這樣的場景一直都是不間斷的。
單獨給秒殺建立一個數據庫,為秒殺服務,表的設計也是竟可能的簡單點,現在的網際網路架構部署都是分庫的。
至於表就看大家怎麼設計了,該設定索引的地方還是要設定索引的,建完後記得用explain看看SQL的執行計劃。(不了解的小夥伴也沒事,MySQL章節去康康)
分散式事務這為啥我不放在後端而放到最後來講呢?
因為上面的任何一步都是可能出錯的,而且我們是在不同的服務裡面出錯的,那就涉及分散式事務了,但是分散式事務大家想的是一定要成功什麼的那就不對了,還是那句話,幾個請求丟了就丟了,要保證時效和服務的可用可靠。
所以TCC和最終一致性其實不是很適合,TCC開發成本很大,所有介面都要寫三次,因為涉及TCC的三個階段。
最終一致性基本上都是靠輪訓的操作去保證一個操作一定成功,那時效性就大打折扣了。
大家覺得不那麼可靠的**兩段式(2PC)和三段式(3PC)**就派上用場了,他們不一定能保證資料最終一致,但是效率上還算ok。
總結到這裡我想我已經基本上把該考慮的點還有對應的解決方案也都說了一下,不知道還有沒有沒考慮到的,但是就算沒考慮到我想我這個設計,應該也能撐住一個完整的秒殺流程。
最後大家再看看這個秒殺系統或許會有新的感悟,是不是一個系統真的沒有大家想的那麼簡單,而且我還是有漏掉的細節,這是一定的。
總結
我們玩歸玩,鬧歸鬧,別拿面試開玩笑。
秒殺不一定是每個同學都會問到的,至少肯定沒Redis基礎那樣常問,但是一旦問到,大家一定要回答到點上。
至少你得說出可能出現的情況,需要注意的情況,以及對於的解決思路和方案,因為這才是一個coder的基本素養,這些你不考慮你也很難去進步。
最後就是需要對整個鏈路比較熟悉,注意是一個完整的鏈路,前端怎麼設計的呀,閘道器的作用呀,怎麼解決Redis的併發競爭啊,資料的同步方式呀,MQ的作用啊等等,相信你會有不錯的收穫。
絮叨另外,敖丙把自己的面試文章整理成了一本電子書,共 1630頁!目錄如下,還有我複習時總結的面試題以及簡歷模板
現在免費送給大家,回覆 【888】 即可獲取。
我是敖丙,你知道的越多,你不知道的越多,我們下期見!
人才們的 【三連】 就是敖丙創作的最大動力,如果本篇部落格有任何錯誤和建議,歡迎人才們留言!
-
1 #
-
2 #
一看就是沒搞過的,在摸索
-
3 #
體彩、福彩:都是我玩剩下的!你們都是弟弟
沒看完,但是我覺得有一段邏輯不對,超賣時,要麼平臺封你號,我都是開發人員呀,平臺封我號?