
電商大促最怕什么?最怕的就是用戶蜂擁而來(lái),系統(tǒng)卻癱了——頁(yè)面刷不開(kāi)、商品加不了購(gòu)物車(chē)、訂單提交失敗、支付一直轉(zhuǎn)圈圈。尤其像小程序這樣輕量級(jí)的入口,平時(shí)跑得好好的,一到秒殺、搶券、大促這種“流量洪峰”的時(shí)刻,如果沒(méi)做好準(zhǔn)備,分分鐘就“崩”給你看。
今天咱們就用大白話,拆解一下小程序電商要扛住高并發(fā)、安穩(wěn)度過(guò)大促,背后的技術(shù)架構(gòu)到底是怎么設(shè)計(jì)的。核心思想就一句話:把大流量“化整為零”,再層層攔截,讓每個(gè)環(huán)節(jié)都游刃有余。
小程序入口簡(jiǎn)單,點(diǎn)開(kāi)就用。當(dāng)幾萬(wàn)、幾十萬(wàn)甚至上百萬(wàn)人同時(shí)涌進(jìn)來(lái),壓力會(huì)像海嘯一樣拍向你的系統(tǒng)。主要壓力點(diǎn)集中在:
首頁(yè)和活動(dòng)頁(yè):所有人進(jìn)來(lái)第一件事就是刷頁(yè)面,看活動(dòng)。
商品詳情頁(yè):看商品圖片、描述、價(jià)格、庫(kù)存。
搜索和推薦:不停地搜東西、找商品。
購(gòu)物車(chē)和下單:把商品加購(gòu),然后提交訂單。
支付:最后的臨門(mén)一腳。
其中,商品庫(kù)存查詢/扣減、訂單創(chuàng)建、支付這幾個(gè)環(huán)節(jié),因?yàn)樯婕白x寫(xiě)核心數(shù)據(jù),是“壓力山大”中的“山大王”,最容易出問(wèn)題。
想象一下體育場(chǎng)散場(chǎng),如果所有人都涌向一個(gè)大門(mén),肯定擠爆。好的做法是:在座位區(qū)就先分流(分區(qū)退場(chǎng)),走到通道有護(hù)欄引導(dǎo)(緩沖),出口有好幾個(gè)門(mén)(分散),門(mén)外還有廣場(chǎng)可以聚集(緩沖)。
我們的系統(tǒng)設(shè)計(jì)也一樣,目標(biāo)是?不讓壓力直接沖垮最脆弱的數(shù)據(jù)庫(kù)。總體架構(gòu)可以分為“三板斧”:
第一板斧:把壓力“擋”在外面(前端+網(wǎng)絡(luò)層優(yōu)化)
第二板斧:把壓力“分”而治之(應(yīng)用服務(wù)層優(yōu)化)
第三板斧:把壓力“消化”在池子里(數(shù)據(jù)層優(yōu)化)
下面我們一道一道防線詳細(xì)說(shuō)。
目標(biāo):讓無(wú)效、重復(fù)的請(qǐng)求,盡量別走到服務(wù)器。
小程序本地緩存:像商品頭圖、活動(dòng)規(guī)則文案、圖標(biāo)這些不怎么變的內(nèi)容,可以緩存在小程序本地。用戶第二次打開(kāi)時(shí),先顯示本地內(nèi)容,再悄悄去后臺(tái)更新。這能節(jié)省大量網(wǎng)絡(luò)請(qǐng)求。
靜態(tài)資源“搬家”:商品詳情頁(yè)里的大圖片、視頻、CSS/JS文件,全都放到專門(mén)的對(duì)象存儲(chǔ)和內(nèi)容分發(fā)網(wǎng)絡(luò)上。這些服務(wù)天生就是為了海量文件分發(fā)而設(shè)計(jì)的,帶寬大、節(jié)點(diǎn)多,能把資源快速推到用戶身邊,讓你的核心服務(wù)器專心處理動(dòng)態(tài)數(shù)據(jù)。
防刷與限流:
惡意請(qǐng)求攔截:在流量入口(比如API網(wǎng)關(guān))設(shè)置規(guī)則,識(shí)別并攔截機(jī)器刷單、惡意爬蟲(chóng)等異常流量。
用戶端限流:比如“搶購(gòu)”按鈕,用戶點(diǎn)擊后立刻變成“請(qǐng)求中”,并在前端設(shè)置一個(gè)冷卻時(shí)間(比如2秒內(nèi)不能重復(fù)點(diǎn)擊),防止用戶瘋狂連點(diǎn)產(chǎn)生一堆無(wú)效請(qǐng)求。
降級(jí)與熔斷:當(dāng)發(fā)現(xiàn)某個(gè)服務(wù)(比如“用戶積分查詢”)響應(yīng)太慢或掛了,立刻“掐斷”對(duì)這個(gè)服務(wù)的調(diào)用,暫時(shí)返回一個(gè)默認(rèn)值(比如“積分暫不可用”),或者隱藏相關(guān)功能模塊。寧可讓部分功能不可用,也要保住核心的下單、支付流程暢通。?這就是“丟車(chē)保帥”。
目標(biāo):讓請(qǐng)求分散到不同的“小服務(wù)”和“小節(jié)點(diǎn)”上,避免單點(diǎn)被打爆。
微服務(wù)架構(gòu):別把系統(tǒng)做成一個(gè)“大泥球”。把它拆開(kāi)!用戶服務(wù)、商品服務(wù)、訂單服務(wù)、庫(kù)存服務(wù)、支付服務(wù)……?每個(gè)服務(wù)獨(dú)立開(kāi)發(fā)、部署、擴(kuò)容。大促時(shí),只需要重點(diǎn)擴(kuò)容壓力最大的商品和訂單服務(wù)集群就行了。一個(gè)服務(wù)出問(wèn)題,不影響別的(比如搜索掛了,但下單還能用)。
負(fù)載均衡:在每個(gè)微服務(wù)前面,放一個(gè)負(fù)載均衡器(就像公司的前臺(tái)接待)。用戶請(qǐng)求來(lái)了,它均勻地分發(fā)給后面成百上千臺(tái)應(yīng)用服務(wù)器中的某一臺(tái),確保沒(méi)有一臺(tái)服務(wù)器累死,其他的閑死。
集群化與彈性伸縮:別指望靠一兩臺(tái)“神機(jī)”扛所有流量。要用“機(jī)海戰(zhàn)術(shù)”,準(zhǔn)備一個(gè)由大量普通服務(wù)器組成的集群。而且這個(gè)集群要能彈性伸縮:大促前,根據(jù)預(yù)測(cè)自動(dòng)增加服務(wù)器;大促后,自動(dòng)減少,節(jié)省成本。
異步化與消息隊(duì)列:這是解耦和削峰的神器!別讓用戶什么都等著。
場(chǎng)景一:下單。用戶提交訂單,系統(tǒng)立刻返回“下單成功,正在處理”。然后把生成訂單詳情、扣庫(kù)存、發(fā)短信通知等耗時(shí)操作,放進(jìn)一個(gè)叫?“消息隊(duì)列”?的郵箱里,讓后臺(tái)服務(wù)慢慢去取出來(lái)處理。這樣用戶支付體驗(yàn)極快,后臺(tái)壓力也平緩了。
場(chǎng)景二:秒殺。百萬(wàn)用戶同時(shí)點(diǎn)“立即購(gòu)買(mǎi)”,把他們的請(qǐng)求先放進(jìn)隊(duì)列排隊(duì),系統(tǒng)按自己的能力逐個(gè)處理,告訴隊(duì)列后面的人“庫(kù)存不足”。這比所有人同時(shí)去搶數(shù)據(jù)庫(kù)里那一條庫(kù)存記錄要文明得多。
目標(biāo):守住最后一道,也是最關(guān)鍵的防線——數(shù)據(jù)庫(kù)。
緩存之王:Redis:這是應(yīng)對(duì)高并發(fā)的定海神針。把那些讀多寫(xiě)少、變化不快的數(shù)據(jù),全塞進(jìn)Redis這種內(nèi)存數(shù)據(jù)庫(kù)里。
商品信息:詳情頁(yè)的標(biāo)題、價(jià)格(注意,庫(kù)存要特殊處理)。
活動(dòng)配置:大促的規(guī)則、優(yōu)惠券信息。
用戶會(huì)話:用戶登錄狀態(tài)。
熱點(diǎn)數(shù)據(jù):被瘋狂訪問(wèn)的某幾個(gè)爆款商品。
請(qǐng)求來(lái)了,先去Redis里找,99%的請(qǐng)求可能在這里就被滿足并返回了,根本不會(huì)去碰慢吞吞的數(shù)據(jù)庫(kù)。?這叫?“讀緩存”。
數(shù)據(jù)庫(kù)的“讀寫(xiě)分離”:數(shù)據(jù)庫(kù)通常一臺(tái)機(jī)器既要負(fù)責(zé)寫(xiě)(下單、支付),又要負(fù)責(zé)讀(查商品、查訂單),忙不過(guò)來(lái)。那就“主從分離”:主數(shù)據(jù)庫(kù)只負(fù)責(zé)寫(xiě),多個(gè)從數(shù)據(jù)庫(kù)只負(fù)責(zé)讀。應(yīng)用服務(wù)器查數(shù)據(jù)的時(shí)候,去從庫(kù)查;寫(xiě)數(shù)據(jù)的時(shí)候,才找主庫(kù)。這樣讀的壓力就被多個(gè)從庫(kù)分?jǐn)偭恕?/p>
數(shù)據(jù)庫(kù)分庫(kù)分表:當(dāng)訂單表大到幾十億條,再牛的單一數(shù)據(jù)庫(kù)也扛不住。這時(shí)候就要“分家”。
分庫(kù):按業(yè)務(wù)分,用戶數(shù)據(jù)放一個(gè)庫(kù),訂單數(shù)據(jù)放一個(gè)庫(kù)。
分表:按訂單ID的哈希值或者下單時(shí)間,把一張大訂單表拆分成很多張小表(比如order_001,?order_002……)。這樣查詢和維護(hù)的壓力就分散到多臺(tái)機(jī)器上了。
庫(kù)存扣減的“終極方案”:秒殺場(chǎng)景下,庫(kù)存是最熱的“熱點(diǎn)數(shù)據(jù)”。絕不能直接用數(shù)據(jù)庫(kù)去查和扣,會(huì)鎖死。
方案一:Redis預(yù)扣減。大促開(kāi)始前,把商品庫(kù)存數(shù)量加載到Redis里。用戶下單時(shí),用Redis的原子操作(DECR)直接在內(nèi)存里扣減。扣成功了,再異步通知數(shù)據(jù)庫(kù)完成最終扣減。這樣可以扛住極高的瞬時(shí)并發(fā)。
方案二:隊(duì)列串行化。如上所述,所有下單請(qǐng)求排隊(duì),一個(gè)一個(gè)處理,雖然用戶體驗(yàn)上稍有延遲,但絕對(duì)保證不亂、不超賣(mài)。
技術(shù)設(shè)計(jì)得再好,沒(méi)經(jīng)過(guò)實(shí)戰(zhàn)檢驗(yàn)都是紙上談兵。所以,大促前必須做全鏈路壓測(cè)。
簡(jiǎn)單說(shuō),就是在線上環(huán)境,用機(jī)器模擬出比預(yù)期大促流量還高的用戶,按照真實(shí)的購(gòu)物流程(瀏覽->加購(gòu)->下單->支付),完整地“攻擊”一遍自己的系統(tǒng)。這個(gè)過(guò)程中:
會(huì)發(fā)現(xiàn)哪里是性能瓶頸(比如某個(gè)接口慢、某個(gè)數(shù)據(jù)庫(kù)CPU滿了)。
會(huì)驗(yàn)證緩存、降級(jí)、熔斷策略是否生效。
會(huì)測(cè)試彈性伸縮是否靈敏。
最重要的是,讓團(tuán)隊(duì)在真正的大流量來(lái)臨前,心里有底。
我們可以把整個(gè)架構(gòu)想象成一場(chǎng)演唱會(huì):
小程序的本地緩存和CDN?= 場(chǎng)外的大屏幕和廣播,讓沒(méi)擠進(jìn)去的人也能感受氛圍(減輕入口壓力)。
負(fù)載均衡和微服務(wù)集群?= 多個(gè)檢票口和不同的功能區(qū)(商品區(qū)、訂單區(qū)),有效分流觀眾。
Redis緩存?= 場(chǎng)內(nèi)隨處可見(jiàn)的引座員和指示牌,快速解答大部分疑問(wèn),不用事事都去問(wèn)總控臺(tái)(數(shù)據(jù)庫(kù))。
消息隊(duì)列?= 排隊(duì)購(gòu)買(mǎi)紀(jì)念品的隊(duì)列,讓大家有序等待,避免一窩蜂擠垮柜臺(tái)。
數(shù)據(jù)庫(kù)讀寫(xiě)分離和分庫(kù)分表?= 強(qiáng)大的后臺(tái)倉(cāng)庫(kù)管理和財(cái)務(wù)系統(tǒng),雖然處理核心事務(wù)慢一點(diǎn),但前面層層保護(hù),讓它能從容工作。
全鏈路壓測(cè)?= 演唱會(huì)前的帶妝彩排和應(yīng)急演練。
所以,解決小程序高并發(fā)、設(shè)計(jì)電商大促不崩的架構(gòu),沒(méi)有銀彈,而是一套組合拳。核心就是:前端做緩沖,服務(wù)做拆分,數(shù)據(jù)做緩存,熱點(diǎn)做隔離,數(shù)據(jù)庫(kù)做保護(hù),一切靠演練。?通過(guò)這種層層設(shè)防、分而治之的策略,才能讓系統(tǒng)在流量洪峰面前,穩(wěn)如磐石。