自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

秒殺系統(tǒng)架構(gòu)解析:應(yīng)對(duì)高并發(fā)的藝術(shù)

開發(fā)
從個(gè)人角度來看,秒殺系統(tǒng)的設(shè)計(jì)套路往往適用于其他高并發(fā)場(chǎng)景,具有較高的借鑒價(jià)值。同時(shí),其特殊的挑戰(zhàn)和需求。

作者 | 蔡佳新

秒殺活動(dòng)是大家耳熟能詳?shù)馁?gòu)物方式,它伴隨著一個(gè)極端的場(chǎng)景:極度的商品供不應(yīng)求。這里面既有需求真實(shí)且迫切的用戶,也有試圖從中牟利的黃牛。反應(yīng)到系統(tǒng)應(yīng)對(duì)的挑戰(zhàn)上,就是相較于以往千倍萬倍的用戶規(guī)模,可能是真人可能是機(jī)器人,在同一瞬間對(duì)系統(tǒng)發(fā)起沖擊,需要海量的計(jì)算資源才能支撐。

對(duì)于各大電商平臺(tái)而言,爆款運(yùn)營(yíng)和促銷活動(dòng)的日常化已成為常態(tài),而支撐這些的秒殺系統(tǒng)自然是不可或缺的一環(huán)。同時(shí),秒殺活動(dòng)的巨大流量就像一頭洪荒之獸,若控制不當(dāng),可能會(huì)沖擊整個(gè)交易體系。因此,秒殺系統(tǒng)在交易體系中便扮演著至關(guān)重要的角色。

從個(gè)人角度來看,秒殺系統(tǒng)的設(shè)計(jì)套路往往適用于其他高并發(fā)場(chǎng)景,具有較高的借鑒價(jià)值。同時(shí),其特殊的挑戰(zhàn)和需求,需要架構(gòu)師在設(shè)計(jì)中權(quán)衡考量,這也有助于培養(yǎng)個(gè)人在權(quán)衡取舍方面的能力。

無損的技術(shù)方案

應(yīng)對(duì)高并發(fā)就好比應(yīng)對(duì)水患:

  • 通過分流讓支流分?jǐn)倝毫?,隔離風(fēng)險(xiǎn)。對(duì)應(yīng)到軟件設(shè)計(jì)就是系統(tǒng)隔離,分割流量;
  • 通過建造水庫(kù)存儲(chǔ)洪水,再緩慢排出,削峰填谷。對(duì)應(yīng)到軟件設(shè)計(jì)就是無損消峰;
  • 通過拓寬河道,清除淤沙提高水流流速。對(duì)應(yīng)到軟件設(shè)計(jì)就是常說的性能優(yōu)化,比如通用的多級(jí)緩存,特定場(chǎng)景的高性能庫(kù)存扣減。

因?yàn)楹罄m(xù)方案會(huì)圍繞請(qǐng)求經(jīng)過的多個(gè)層級(jí)展開,所以在介紹方案之前,我們需要先了解一個(gè)基本情況:一個(gè)請(qǐng)求打到服務(wù)器的基本鏈路為:DNS->網(wǎng)關(guān)->前端/后端,其中流量峰值也應(yīng)該逐層減少。如圖:

1.系統(tǒng)隔離

分布式系統(tǒng)由一個(gè)個(gè)發(fā)布單元共同組成。獨(dú)立的發(fā)布單元可以按需做容量伸縮,也可以在發(fā)生故障時(shí)及時(shí)做故障隔離,以保證不會(huì)出現(xiàn)個(gè)別服務(wù)故障導(dǎo)致整個(gè)系統(tǒng)不可用的情況。

秒殺活動(dòng)因?yàn)槠涓叻逯档奶匦?,所以一般我們?huì)把它隔離出來,成一個(gè)獨(dú)立的秒殺系統(tǒng)(常規(guī)服務(wù)都是按領(lǐng)域特性做縱切,但這里我們按品類做橫切,帶有秒殺活動(dòng)標(biāo)的商品將會(huì)分流到獨(dú)立的秒殺系統(tǒng)集群)。

考慮到交易系統(tǒng)體量是很大的,如果為秒殺品類把整個(gè)交易系統(tǒng)都復(fù)制一份,那成本就太大了。所以我們把隔離區(qū)分為物理隔離和邏輯隔離:

  • 把需要定制化邏輯的能力和有特殊非功能性要求的能力剝離出來做物理隔離
  • 標(biāo)準(zhǔn)化且沒有特殊非功能要求的能力就采用邏輯隔離。

畫成部署架構(gòu)如下:

首先,我們采用獨(dú)立的秒殺域名和nginx集群(物理隔離)。這樣:

  • 可以隔離流量大頭,防止峰值沖擊交易系統(tǒng)(非功能性需求);
  • 能夠靈活擴(kuò)展,針對(duì)不同時(shí)段的流量預(yù)估擴(kuò)展nginx及后邊服務(wù)的規(guī)模(非功能性需求);
  • 能夠靈活增減私有的防控邏輯,而不影響原交易系統(tǒng)(定制化邏輯)。

接著,我們將商詳頁和下單頁獨(dú)立部署(前端+BFF,物理隔離)?;诿霘⒒顒?dòng)的玩法特征,海量用戶在活動(dòng)快開始時(shí)會(huì)反復(fù)刷新商詳頁,在活動(dòng)開始時(shí)又會(huì)瞬時(shí)并發(fā)到訪問下單頁,所以這兩個(gè)頁面都是承受流量沖擊的大頭,需要隔離開(非功能性需求)。同時(shí)因?yàn)槊霘⒒顒?dòng)的特性,商品屬于極端供不應(yīng)求的場(chǎng)景,賣家占優(yōu)勢(shì),所以可以做服務(wù)降級(jí),以降低計(jì)算資源消耗、提高性能(定制化邏輯)。比如:

  • 商詳頁可以把履約時(shí)效拿掉,不再計(jì)算預(yù)計(jì)多久能到貨。還可以拿掉評(píng)價(jià)信息,不再展示評(píng)價(jià);
  • 下單頁可以不再計(jì)算優(yōu)惠金額分?jǐn)偅霘⑸唐凡粎⑴c任何疊加的優(yōu)惠活動(dòng)。僅保留必要的信息,比如商品信息,商品主圖,購(gòu)買按鈕,下單按鈕等等。
  • 至于結(jié)算頁、收銀臺(tái)看情況,如果流量壓力不大可以不用做物理隔離的(支付扣減庫(kù)存下單的壓力會(huì)直接傳遞到結(jié)算,但下單扣減就不需要并發(fā)支付,僅搶到的用戶需要結(jié)算,壓力就很小了。秉著降低流量影響面積的角度,這里假設(shè)下單扣減,畢竟秒殺商品也不怕客戶下單后不支付)。

最后,商品購(gòu)買成功還需要依賴,訂單系統(tǒng)創(chuàng)建訂單,庫(kù)存系統(tǒng)扣減商品庫(kù)存,結(jié)算系統(tǒng)確認(rèn)支付等等步驟。到達(dá)這里流量相對(duì)已經(jīng)比較平穩(wěn),并且邏輯上沒有什么定制化訴求(壓力小了,沒必要圍繞性能做定制化了),所以就采用邏輯隔離復(fù)用原交易系統(tǒng)的集群。邏輯隔離有兩種實(shí)現(xiàn)思路:

  • 第一種是依賴限流框架,比如在訂單系統(tǒng)設(shè)置來源是秒殺系統(tǒng)BFF的創(chuàng)建訂單請(qǐng)求,TPS不能超過100,并發(fā)連接數(shù)不能超過20;
  • 第二種是依賴RPC框架,RPC框架可以設(shè)置分組,只要把訂單系統(tǒng)集群里面部分服務(wù)節(jié)點(diǎn)設(shè)置成“秒殺組”,再把秒殺服務(wù)BFF的客戶端也設(shè)置為“秒殺組”,那么秒殺系統(tǒng)的流量就只會(huì)打到訂單系統(tǒng)集群里面屬于“秒殺組”的節(jié)點(diǎn)上。這種隔離方式分割了集群,集群節(jié)點(diǎn)少了,出現(xiàn)故障發(fā)生過載的可能就提高了,可能會(huì)導(dǎo)致秒殺系統(tǒng)不可用。

為什么集群節(jié)點(diǎn)少了,出現(xiàn)故障發(fā)生過載的可能就提高了?就好比公里原本4條道能并行4輛車,現(xiàn)在給按車輛類型分成了機(jī)動(dòng)車和公交車專用,機(jī)動(dòng)車道2條。如果其中1條機(jī)動(dòng)車道發(fā)生車禍,原本分散在2條道上的車流就要匯聚在1條道,原本順暢的通行可能立馬就開始堵車了。

2.多級(jí)緩存

多級(jí)緩存,無非就是在系統(tǒng)的多個(gè)層級(jí)進(jìn)行數(shù)據(jù)緩存,以提高響應(yīng)效率,這是高并發(fā)架構(gòu)中最常用的方案之一。

(1) DNS層

一般我們會(huì)將靜態(tài)資源掛到CDN上,借助CDN來分流和提高響應(yīng)效率。以秒殺系統(tǒng)為例,就是將秒殺前端系統(tǒng)的商詳頁和下單頁緩存到CDN網(wǎng)絡(luò)上。一個(gè)借助CDN的用戶請(qǐng)求鏈路如下:

如果用戶終端有頁面緩存就走終端本地緩存,沒有就請(qǐng)求遠(yuǎn)端CDN的域名(靜態(tài)資源走CDN域名),請(qǐng)求來到DNS調(diào)度的節(jié)點(diǎn),調(diào)度一個(gè)最近的CDN節(jié)點(diǎn),如果該CDN節(jié)點(diǎn)有頁面緩存則返回,沒有則向緣站發(fā)起溯源,請(qǐng)求就會(huì)走普通鏈路過秒殺系統(tǒng)ng到秒殺系統(tǒng)前端。

(2) 網(wǎng)關(guān)層

網(wǎng)關(guān)這個(gè)有多種組合情況,最簡(jiǎn)單的就是一個(gè)接入層網(wǎng)關(guān)加一個(gè)應(yīng)用層網(wǎng)關(guān),比如:ISV(四層)-> Nginx(七層)。以這個(gè)為例,這里的緩存優(yōu)化主要看接入層的負(fù)載均衡算法和應(yīng)用層的本地緩存和集中內(nèi)存緩存。

之所以說緩存還要提負(fù)載均衡算法,是因?yàn)楣?jié)點(diǎn)的本地緩存的有效性和負(fù)載均衡算法是強(qiáng)綁定的。常用的負(fù)載均衡算法有輪詢(也叫取模)和一致性哈希。輪詢可以讓請(qǐng)求分發(fā)更均衡,但同個(gè)緩存key的請(qǐng)求不一定會(huì)路由到同個(gè)應(yīng)用層Nginx上,Nginx的本地緩存命中率低。一致性哈??梢宰屚瑐€(gè)緩存key路由到同個(gè)應(yīng)用層Nginx上,Nginx的本地緩存命中率高,但其請(qǐng)求分發(fā)不均衡容易出現(xiàn)單機(jī)熱點(diǎn)問題。有一種做法是設(shè)置一個(gè)閾值,當(dāng)單節(jié)點(diǎn)請(qǐng)求超過閾值時(shí)改為輪詢,可以算是自適應(yīng)性負(fù)載均衡的變種。但這種基于閾值判斷的做法在應(yīng)對(duì)真正的高并發(fā)時(shí)效果并不理想。

所以想要運(yùn)用本地緩存強(qiáng)依賴業(yè)務(wù)運(yùn)營(yíng),需要對(duì)每個(gè)熱點(diǎn)商品key有較為準(zhǔn)確的流量預(yù)估,并人為的組合這些商品key,進(jìn)而控制流量均勻的落到每個(gè)應(yīng)用層Nginx上(其實(shí)就是數(shù)據(jù)分片,然后每片數(shù)據(jù)流量一致)。這非常困難,所以筆者認(rèn)為,還是采用輪詢加集中內(nèi)存緩存比較簡(jiǎn)單有效。

一個(gè)從接入層開始帶有本地緩存和集中內(nèi)存緩存的請(qǐng)求鏈路如下:

(3) 服務(wù)層

應(yīng)用層ngnix->秒殺系統(tǒng)BFF->訂單服務(wù),其實(shí)兩兩組合和網(wǎng)關(guān)層是一樣的場(chǎng)景。應(yīng)用層ngnix基于ngnix的負(fù)載均衡轉(zhuǎn)發(fā)請(qǐng)求到秒殺系統(tǒng)BFF,秒殺系統(tǒng)BFF基于RPC框架的負(fù)載均衡轉(zhuǎn)發(fā)請(qǐng)求到訂單服務(wù)。都面臨著負(fù)載均衡策略選擇和是否啟用本地緩存的問題。不一樣的點(diǎn)只是緩存的粒度和啟用緩存的技術(shù)棧選擇。

(4) 多級(jí)緩存失效

多級(jí)緩存因?yàn)榫彺娣稚⒌蕉鄠€(gè)層級(jí),所以很難用單一的技術(shù)棧來應(yīng)對(duì)緩存失效的問題,但都等到緩存過期,這種更新時(shí)延較長(zhǎng)又不一定能被業(yè)務(wù)接受。所以這里就再提下這個(gè)話題。有一個(gè)做法是基于DB的binlog監(jiān)聽,各層監(jiān)聽自己相關(guān)的binlog信息,在發(fā)生緩存被變更的情況時(shí),及時(shí)讓集成內(nèi)存的緩存失效。本地緩存在這里還有個(gè)缺陷,就是緩存失效時(shí)需要廣播到所有節(jié)點(diǎn),讓每個(gè)節(jié)點(diǎn)都失效,對(duì)于頻繁變更的熱key就可能產(chǎn)生消息風(fēng)暴。

3.無損消峰

秒殺活動(dòng)的特點(diǎn)是瞬時(shí)高峰的流量,就像一座高聳的尖塔,短時(shí)間內(nèi)涌入大量請(qǐng)求。為這個(gè)峰值準(zhǔn)備對(duì)應(yīng)的服務(wù)集群,首先成本太高,接著單純的水平擴(kuò)展也不一定能做到(分布式架構(gòu)存在量變引起質(zhì)變的問題,資源擴(kuò)展到一定量級(jí),原先的技術(shù)方案整個(gè)就不適用了。比如,當(dāng)集群節(jié)點(diǎn)太多,服務(wù)注冊(cè)發(fā)現(xiàn)可能會(huì)有消息風(fēng)暴;出入口的帶寬出現(xiàn)瓶頸,需要在部署上分流)。更別說這個(gè)峰值也不受控制,想要高枕無憂就會(huì)有很高的冗余浪費(fèi)。

所以一般我們會(huì)采用消峰的方式:

  • 一種是直接斷頭,把超出負(fù)荷的流量直接都丟棄掉,也就是我們常見的限流,也稱為有損消峰(如果這是大促的訂單,砍掉的可能都是錢,這個(gè)有損是真的資損);
  • 另一種就是分流,也叫消峰填谷,通過技術(shù)或者業(yè)務(wù)手段將請(qǐng)求錯(cuò)開,鋪到更長(zhǎng)的時(shí)間線上,從而降低峰值,常見的有MQ異步消費(fèi)和驗(yàn)證碼問答題。

這里我們先聊下無損消峰,有損放后邊談。

(1) MQ異步消費(fèi)

MQ依賴三個(gè)特性可以做到平滑的最終一致,分別是消息堆積,勻速消費(fèi)和至少成功一次:

  • 有消息堆積才能起到蓄水池的效果,在出水口流速恒定的情況下能接住入水口瞬時(shí)的大流量;
  • 有勻速消費(fèi)才能讓下游集群的流量壓力恒定,不會(huì)被沖擊;
  • 有至少成功一次,才能保證事物最終一致。

以秒殺系統(tǒng)BFF下單操作向訂單服務(wù)創(chuàng)建訂單為例。如果沒有消息隊(duì)列(MQ),同時(shí)有100W個(gè)創(chuàng)建請(qǐng)求,訂單系統(tǒng)就必須承擔(dān)100W個(gè)并行連接的壓力。但是,如果使用了MQ,那么100W個(gè)創(chuàng)建請(qǐng)求的壓力將全部轉(zhuǎn)移到MQ服務(wù)端,訂單系統(tǒng)只需要維持64個(gè)并行連接,以穩(wěn)定地消費(fèi)MQ服務(wù)端的消息。

這樣一來,訂單系統(tǒng)的集群規(guī)模就可以大大減小,而且更重要的是,系統(tǒng)的穩(wěn)定性得到了保障。由于并行連接數(shù)的減少,資源競(jìng)爭(zhēng)也會(huì)降低,整體響應(yīng)效率也會(huì)提高,就像在食堂排隊(duì)打飯一樣,有序排隊(duì)比亂搶效率更高。但是,用戶體驗(yàn)可能會(huì)受到影響,因?yàn)辄c(diǎn)擊搶購(gòu)后可能會(huì)收到排隊(duì)提示(其實(shí)就是友好提示),需要延遲幾十秒甚至幾分鐘才能收到搶購(gòu)結(jié)果。

(2) 驗(yàn)證碼問答題

引入驗(yàn)證碼問答題其實(shí)有兩層好處,一層是消峰,用戶0.5秒內(nèi)并發(fā)的下單事件,因?yàn)閭€(gè)人的手速差異,被平滑的分散到幾秒甚至幾十秒中;另外一層是防刷,提高機(jī)器作弊的成本。

① 驗(yàn)證碼

基本實(shí)現(xiàn)步驟如下:

  • 請(qǐng)求到來時(shí)生成1串6位隨機(jī)字符串 verification_code
  • 用特定前綴拼接用戶ID作為key,verification_code做為value存redis,超時(shí)5s
  • 生成一個(gè)圖片,將 verification_code 寫到圖片上,返回給用戶
  • 用戶輸入圖片中字符串
  • 從redis里面取出 verification_code 做比對(duì),如果一致,執(zhí)行下單操作

但這樣其實(shí)是可以用暴力破解的,比如,用機(jī)器仿照一個(gè)用戶發(fā)起10W個(gè)請(qǐng)求攜帶不同的6位隨機(jī)字符。所以校驗(yàn)驗(yàn)證碼時(shí)可以使用 GETDEL ,讓驗(yàn)證碼校驗(yàn)無論對(duì)錯(cuò)都讓驗(yàn)證碼失效。

② 問答題

基本實(shí)現(xiàn)思路和驗(yàn)證碼幾乎一樣。差別在于,問答題的題庫(kù)要提前生成,請(qǐng)求到來時(shí)從題庫(kù)中拿到一組問題和答案。然后把答案存redis,問題塞到圖片里返回給用戶。

驗(yàn)證碼和問答題具有很好的消峰效果。特別是問答題,想要提高消峰效果只要提高問題難度就行,例如,筆者曾經(jīng)在12306上連續(xù)錯(cuò)了十幾次問答題。但是這也是用戶體驗(yàn)有損的,例如,雖然筆者當(dāng)初未能成功搶到票而感到沮喪,但這魔性的題庫(kù)依然把筆者成功逗笑。

無損消峰,無損了流量,但損失了用戶體驗(yàn)。現(xiàn)如今技術(shù)水平在不斷進(jìn)步,解決方法在增多,這些有損用戶體驗(yàn)的技術(shù)方案可能都會(huì)慢慢退出歷史舞臺(tái),就像淘寶取消618預(yù)售。

4.庫(kù)存扣減

我們知道,用戶購(gòu)買商品需要扣減庫(kù)存,扣減庫(kù)存需要查詢庫(kù)存是否足夠,足夠就占用庫(kù)存,不夠則返回庫(kù)存不足(這里不區(qū)分庫(kù)存可用、占用、已消耗等狀態(tài),統(tǒng)一成扣減庫(kù)存數(shù)量,簡(jiǎn)化場(chǎng)景)。

在并發(fā)場(chǎng)景,如果查詢庫(kù)存和扣減庫(kù)存不具備原子性,就有可能出現(xiàn)超賣,而高并發(fā)場(chǎng)景超賣的出現(xiàn)概率會(huì)增高,超賣的數(shù)額也會(huì)增高。處理超賣問題是件麻煩事,一方面,系統(tǒng)全鏈路刷數(shù)會(huì)很麻煩(多團(tuán)隊(duì)協(xié)作),客服外呼也會(huì)有額外成本。另一方面,也是最主要的原因,客戶搶到了訂單又被取消,會(huì)嚴(yán)重影響客戶體驗(yàn),甚至引發(fā)客訴產(chǎn)生公關(guān)危機(jī)。

(1) 實(shí)現(xiàn)邏輯

業(yè)內(nèi)常用的方案就是使用redis+lua,借助redis單線程執(zhí)行+lua腳本中的邏輯可以在一次執(zhí)行中順序完成的特性達(dá)到原子性(原子性其實(shí)不大準(zhǔn)確,叫排它性可能更準(zhǔn)確些,因?yàn)檫@里不具備回滾動(dòng)作,異常情況需要自己回滾)。

lua腳本基本實(shí)現(xiàn)大致如下:

-- 獲取庫(kù)存緩存key KYES[1] = hot_{itemCode-skuCode}_stock
local hot_item_stock = KYES[1]
-- 獲取剩余庫(kù)存數(shù)量
local stock = tonumber(redis.call('get', hot_item_stock)) 
-- 購(gòu)買數(shù)量
local buy_qty = tonumber(ARGV[1])
-- 如果庫(kù)存小于購(gòu)買數(shù)量 則返回 1, 表達(dá)庫(kù)存不足
if stock < buy_qty thenreturn1end
-- 庫(kù)存足夠 
-- 更新庫(kù)存數(shù)量 
stock = stock - buy_qty
redis.call('set', hot_item_stock, tostring(stock))
-- 扣減成功 則返回 2, 表達(dá)庫(kù)存扣減成功
return2end

但這個(gè)腳本具備一些問題:

  • 不具備冪等性,同個(gè)訂單多次執(zhí)行會(huì)出現(xiàn)重復(fù)扣減的問題,手動(dòng)回滾也沒辦法判斷是否會(huì)回滾過,會(huì)出現(xiàn)重復(fù)增加的問題。
  • 不具備可追溯性,庫(kù)存被誰被哪個(gè)訂單扣減了不知道。

結(jié)合以上問題,我們對(duì)方案做些增強(qiáng)。

增強(qiáng)后的lua腳本如下:

-- 獲取庫(kù)存扣減記錄緩存key KYES[2] = hot_{itemCode-skuCode}_deduction_history
-- 使用 Redis Cluster hash tag 保證 stock 和 history 在同個(gè)槽
local hot_deduction_history = KYES[2]
-- 請(qǐng)求冪等判斷,存在返回0, 表達(dá)已扣減過庫(kù)存
local exist = redis.call('hexists', hot_deduction_history, ARGV[2])
if exist = 1thenreturn0end

-- 獲取庫(kù)存緩存key KYES[1] = hot_{itemCode-skuCode}_stock
local hot_item_stock = KYES[1]
-- 獲取剩余庫(kù)存數(shù)量
local stock = tonumber(redis.call('get', hot_item_stock)) 
-- 購(gòu)買數(shù)量
local buy_qty = tonumber(ARGV[1])
-- 如果庫(kù)存小于購(gòu)買數(shù)量 則返回 1, 表達(dá)庫(kù)存不足
if stock < buy_qty thenreturn1end
-- 庫(kù)存足夠 
-- 1.更新庫(kù)存數(shù)量 
-- 2.插入扣減記錄 ARGV[2] = ${扣減請(qǐng)求唯一key}-${扣減類型} 值為 buy_qty
stock = stock - buy_qty
redis.call('set', hot_item_stock, tostring(stock))
redis.call('hset', hot_deduction_history, ARGV[2], buy_qty)
-- 如果剩余庫(kù)存等于0 則返回 2, 表達(dá)庫(kù)存已為0
if stock = 0thenreturn2end
-- 剩余庫(kù)存不為0 返回 3 表達(dá)還有剩余庫(kù)存
return3end
  • 利用Redis Cluster hash tag保證stock和history在同個(gè)槽,這樣lua腳本才能正常執(zhí)行。
  • 利用hot_deduction_history,判斷扣減請(qǐng)求是否執(zhí)行過,以實(shí)現(xiàn)冪等性。
  • 借助hot_deduction_history的value值判斷追溯扣減來源,比如:用戶A的交易訂單A的扣減請(qǐng)求,或者用戶B的借出單B的扣減請(qǐng)求。
  • 回滾邏輯會(huì)先判斷hot_deduction_history里面有沒有 ${扣減請(qǐng)求唯一key} ,有則執(zhí)行回補(bǔ)邏輯,沒有則認(rèn)定回補(bǔ)成功。

但是以上邏輯依舊有漏洞,比如(消息亂序消費(fèi)),訂單扣減庫(kù)存超時(shí)成功觸發(fā)了重新扣減庫(kù)存,但同時(shí)訂單取消觸發(fā)了庫(kù)存扣減回滾,回滾邏輯先成功,超時(shí)成功的重新扣減庫(kù)存就會(huì)成為臟數(shù)據(jù)留在redis里。

處理方案有兩種,一種是追加對(duì)賬,定期校驗(yàn)hot_deduction_history中數(shù)據(jù)對(duì)應(yīng)單據(jù)的狀態(tài),對(duì)于已經(jīng)取消的單據(jù)追加一次回滾請(qǐng)求,存在時(shí)延(業(yè)務(wù)不一定接受)以及額外計(jì)算資源開銷。另一種,是使用有序消息,讓扣減庫(kù)存和回滾庫(kù)存都走同一個(gè)MQ topic的有序隊(duì)列,借助MQ消息的有序性保證回滾動(dòng)作一定在扣減動(dòng)作后面執(zhí)行,但有序串行必然帶來性能下降。

(2) 高可用

存在redis終究是內(nèi)存,一旦服務(wù)中斷,數(shù)據(jù)就消失的干干凈凈。所以需要追加保護(hù)數(shù)據(jù)不丟失的方案。

運(yùn)用redis部署的高可用方案來實(shí)現(xiàn),方案如下:

  • 采用 Redis Cluster(數(shù)據(jù)分片+ 多副本 + 同步多寫 + 主從自動(dòng)選舉)。
  • 多寫節(jié)點(diǎn)分(同城異地)多中心防止意外災(zāi)害。

定期歸檔冷數(shù)據(jù)。定期 + 庫(kù)存為0觸發(fā)redis數(shù)據(jù)往DB同步,流程如下:

CDC分發(fā)數(shù)據(jù)時(shí),秒殺商品,hot_deduction_history的數(shù)據(jù)量不高,可以一次全量同步。但如果是普通大促商品,就需要再追加一個(gè)map動(dòng)作分批處理,以保證每次執(zhí)行CDC的數(shù)據(jù)量恒定,不至于一次性數(shù)據(jù)量太大出現(xiàn)OOM。具體代碼如下:

/**
 * 對(duì)任務(wù)做分發(fā)
 * @param stockKey 目標(biāo)庫(kù)存的key值
 */
public void distribute(String stockKey){
    final String historyKey = StrUtil.format("hot_{}_deduction_history", stockKey);
    // 獲取指定庫(kù)存key 所有扣減記錄的key(一般會(huì)采用分頁獲取,防止數(shù)據(jù)量太多,內(nèi)存波動(dòng),這里偷懶下)
    final List<String> keys  = RedisUtil.hkeys(historyKey, stockKey);
    // 以100為大小,分片所有記錄 key
    final List<List<String>> splitKeys = CollUtil.split(keys, 100);
    // 將集合分發(fā)給各個(gè)節(jié)點(diǎn)執(zhí)行
    map(historyKey, splitKeys);
}
/**
 * 對(duì)單頁任務(wù)做執(zhí)行
 * @param historyKey 目標(biāo)庫(kù)存的key值
 * @param stockKeys  要執(zhí)行的頁面大小
 */
public void mapExec(String historyKey, List<String> stockKeys){
    // 獲取指定庫(kù)存key 指定扣減記錄的map
    final Map<String, String> keys = RedisUtil.HmgetToMap(historyKey, stockKeys);
    keys.entrySet()
        .stream()
        .map(stockRecordFactory::of)
        .forEach(stockRecord ->{
            //(冪等+去重)扣減+保存記錄
            stockConsumer.exec(stockRecord);
            //刪除redis中的 key 釋放空間
            RedisUtil.hdel(historyKey, stockRecord.getRecordRedisKey());
        });
}

(3) 為什么不走DB

商品庫(kù)存數(shù)據(jù)在DB最終會(huì)落到單庫(kù)單表的一行數(shù)據(jù)上。無法通過分庫(kù)分表提高請(qǐng)求的并行度。而在單節(jié)點(diǎn)的場(chǎng)景,數(shù)據(jù)庫(kù)的吞吐遠(yuǎn)不如redis。最基礎(chǔ)的原因:IO效率不是一個(gè)量級(jí),DB是磁盤操作,而且還可能要多次讀盤,redis是一步到位的內(nèi)存操作。

同時(shí),一般DB都是提交讀隔離級(jí)別,為了保證原子性,執(zhí)行庫(kù)存扣減,得加鎖,無論悲觀還是樂觀。這不僅性能差(搶不到鎖要等待),而且因?yàn)榉枪礁?jìng)爭(zhēng),容易出現(xiàn)線程饑餓的問題。而redis是單線程操作,不存在共享變量競(jìng)爭(zhēng)的問題。

有一些優(yōu)化思路,比如,合并扣減,走批降低請(qǐng)求的并行連接數(shù)。但伴隨而來的是集單的時(shí)延,以及按庫(kù)分批的訴求;還有拆庫(kù)存行,商品A100個(gè)庫(kù)存拆成2行商品A50庫(kù)存,然后扣減時(shí)分發(fā)請(qǐng)求,以此提高并行連接數(shù)(多行可落在不同庫(kù)來提高并行連接數(shù))。

但伴隨而來的是復(fù)雜的庫(kù)存行拆分管理(把什么庫(kù)存行在什么時(shí)候拆分到哪些庫(kù)),以及部分庫(kù)存行超賣的問題(加鎖優(yōu)化就又串行了,不加總量還有庫(kù)存,個(gè)別庫(kù)存行不足是允許一定系數(shù)超賣還是返回庫(kù)存不足就是一個(gè)要決策的問題)。

部分頭部電商還是采用弱緩存抗讀(非庫(kù)存不足,不實(shí)時(shí)更新),DB抗寫的方案。這個(gè)的前提在于,通過一系列技術(shù)方案,流量落到庫(kù)存已經(jīng)相對(duì)低且平滑了(扛得住,不用再自己實(shí)現(xiàn)操作原子性)。

有損的技術(shù)方案

秒殺活動(dòng)有極高的瞬時(shí)流量,但僅有極少數(shù)流量可以請(qǐng)求成功。這為我們繞開海量計(jì)算資源采用一些特定方案達(dá)到同樣的活動(dòng)效果提供了空間。因?yàn)榻^大部分流量都是要請(qǐng)求失敗的,是真實(shí)搶購(gòu)庫(kù)存失敗還是被規(guī)則過濾掉失敗,都一樣是失敗,對(duì)于參與者來說是一樣的活動(dòng)體驗(yàn)。所以我們不用耿直地去承接所有流量,變成用一系列過濾手段,公平公正地過濾掉絕大部分流量,僅保留有限的優(yōu)質(zhì)流量可以請(qǐng)求到服務(wù)群即可。

基本思路就是,通過業(yè)務(wù)干預(yù)阻止無效流量,通過有損消峰丟棄超荷流量,通過防刷風(fēng)控?cái)r截非法流量,最終留給下游優(yōu)質(zhì)且少量的流量。如圖:

1.業(yè)務(wù)干預(yù)

(1) 提報(bào)

借助提報(bào)系統(tǒng),商戶開展高壓力的活動(dòng)時(shí)都提早報(bào)備接受審批和調(diào)控。這樣,可以提早知道商品、價(jià)格、活動(dòng)開始時(shí)間、面向什么地域、預(yù)計(jì)參與人數(shù)、會(huì)員要求等等信息。幫助預(yù)估出大致流量,支撐編排活動(dòng)調(diào)整活動(dòng)組合,錯(cuò)位壓力(也能不斷保持熱點(diǎn)),平滑流量,調(diào)整計(jì)算機(jī)資源應(yīng)對(duì)高并發(fā)。設(shè)置參與門檻,阻擋非目標(biāo)人群參與。

(2) 預(yù)約

借助預(yù)約系統(tǒng),對(duì)活動(dòng)做預(yù)熱、幫助預(yù)估大致參與活動(dòng)的人數(shù)幫助評(píng)估計(jì)算資源容量。引入風(fēng)控規(guī)則,提早過濾刷子人群。采用發(fā)放參與資格(類似游戲預(yù)約測(cè)試資格和發(fā)放測(cè)試資格),控制參與人數(shù)大小。結(jié)合提報(bào)系統(tǒng)的參數(shù),過濾非目標(biāo)人群,并盡可能提高參與人員離散度(比如參與證書1W,華南華北華東華西各2500)(假設(shè)中獎(jiǎng)的人影響范圍是一個(gè)圓,人群集中這個(gè)圓就有交集,影響范圍就會(huì)減少,所以會(huì)希望離散些。但也不排除有故意集中發(fā)放創(chuàng)造熱點(diǎn)的營(yíng)銷手段)。

(3) 會(huì)員

借助會(huì)員系統(tǒng),篩選出優(yōu)質(zhì)用戶。愿意購(gòu)買會(huì)員的用戶相對(duì)粘性就比較高(可以借助會(huì)員體系做一些提高用戶粘性的舉措,比如信用分,積分,會(huì)員等級(jí),優(yōu)惠卷等等)。同時(shí)會(huì)員用戶的規(guī)模也能幫助預(yù)估活動(dòng)參與流量。

(4) 限購(gòu)

借助限購(gòu)系統(tǒng),比如加強(qiáng)特定區(qū)域市場(chǎng)覆蓋,從地區(qū)限制,僅華東可以參與購(gòu)買;輿情公關(guān)防控,從用戶限制,自家員工禁止購(gòu)買(不能既做裁判也下場(chǎng)踢球);提高離散度,從商品限制,一次只能購(gòu)買一件,一人一個(gè)月只能購(gòu)買一次。

2.有損消峰

前邊講了分流的無損消峰,這里我們講直接去頭的有損消峰。常規(guī)方案就是采用限流降級(jí)手段,這也是應(yīng)對(duì)高并發(fā)必用的手段。

限流是系統(tǒng)自我保護(hù)的最底層手段。再厲害的系統(tǒng),總有其流量承載的上限,一旦流量突破這個(gè)上限,就會(huì)引起實(shí)例宕機(jī),進(jìn)而發(fā)生系統(tǒng)雪崩,帶來災(zāi)難性后果。所以達(dá)到這個(gè)流量上限后,橫豎都無法再響應(yīng)請(qǐng)求,于是直接拋棄這部分請(qǐng)求,保證有限的流量能夠正常交互便成了最優(yōu)解。

(1) 分層限流

我們知道一個(gè)請(qǐng)求會(huì)走過多個(gè)層級(jí),最終才能到達(dá)響應(yīng)請(qǐng)求的服務(wù)節(jié)點(diǎn)。假設(shè)一個(gè)請(qǐng)求會(huì)走過網(wǎng)關(guān)->單服務(wù)集群->單服務(wù)節(jié)點(diǎn)->單接口這幾個(gè)層級(jí),每個(gè)層級(jí)考慮承載上限的維度和容量都不一樣,所以一般都會(huì)有獨(dú)立的限流規(guī)則。

網(wǎng)關(guān)一般是以一個(gè)路由配置或者一組api的吞吐指標(biāo)進(jìn)行限流,具體配置大致如下:

單服務(wù)集群一般是以整個(gè)集群所有API和所有服務(wù)節(jié)點(diǎn)為吞吐指標(biāo)進(jìn)行限流(不常用),具體配置大致如下:

  • 單服務(wù)節(jié)點(diǎn)一般是以服務(wù)節(jié)點(diǎn)的負(fù)載情況來進(jìn)行限流,比如 Load(綜合計(jì)算值)、CPU、內(nèi)存等等。
  • 單接口一般是以整個(gè)集群的一個(gè)API的吞吐指標(biāo)來進(jìn)行限流。

(2) 熱點(diǎn)參數(shù)限流

除開分層的限流,還有參數(shù)維度的限流。

比如,基于IP地址的吞吐量指標(biāo)做限流。這個(gè)維度,對(duì)公司用戶很不友好。因?yàn)橐话愎揪蛶讉€(gè)IP出口,大家都連著wifi,很容易就觸發(fā)限流。所以,一般參與秒殺活動(dòng)時(shí)還是切換回自己的4G網(wǎng),wifi再快也架不住被限流。

比如,基于熱點(diǎn)商品的吞吐量指標(biāo)做限流。在沒有商品維度限流的情況下,假設(shè)秒殺下單接口的集群并發(fā)限流為100,同一時(shí)間參與秒殺活動(dòng)的商品有10個(gè),商品A在一瞬間就搶占了80并發(fā)連接數(shù),剩下的9商品就只能分?jǐn)?0并發(fā)連接數(shù),這會(huì)嚴(yán)重影響其活動(dòng)體驗(yàn)。

限流的口徑有很多,幸運(yùn)的是它們可以組合使用。這樣就能夠確保服務(wù)在各種場(chǎng)景下都有一個(gè)可靠的底層防護(hù)。

3.防刷風(fēng)控

秒殺活動(dòng)中的供需失衡,也會(huì)吸引黑產(chǎn)用戶借助非常規(guī)手段搶購(gòu)。比如,通過物理或軟件的按鍵精靈,用比正常用戶更快的速度搶購(gòu);通過分析接口模仿下單請(qǐng)求,同時(shí)發(fā)起千萬個(gè)請(qǐng)求,用比正常用戶更高的頻次搶購(gòu)。這些行為不僅破壞了活動(dòng)公平性,威脅到普惠和離散訴求,還對(duì)系統(tǒng)的高并發(fā)峰值帶來了新的量級(jí)的挑戰(zhàn),嚴(yán)重影響活動(dòng)的健康發(fā)展。

(1) 防刷

從更快的速度搶購(gòu)的角度很難區(qū)分是正常用戶還是黑產(chǎn)用戶,但更高頻次是很好被捕捉的,畢竟正常人總不能1秒鐘千萬次的點(diǎn)擊吧。所以我們可以針對(duì)高頻次這個(gè)場(chǎng)景構(gòu)建一些防刷手段。

(2) 基于userID限流

我們可以采用熱點(diǎn)參數(shù)限流的方式,基于用戶ID的吞吐量指標(biāo)做限流。例如,規(guī)定每個(gè)用戶ID每秒僅能發(fā)起兩次請(qǐng)求。并且,我們應(yīng)將此限流措施盡可能地置于請(qǐng)求鏈路的上游,如應(yīng)用網(wǎng)關(guān)上,以便在最外層就隔離掉主要流量,從而減少計(jì)算資源的浪費(fèi)。這樣的限流目的與常規(guī)的有損消峰略有所不同,它不僅旨在保護(hù)服務(wù)的穩(wěn)定性,也在防止黑產(chǎn)用戶的攻擊,以此維護(hù)活動(dòng)的公平性。

(3) 基于黑名單限流

依舊是采用熱點(diǎn)參數(shù)限流的方式。但不再是看吞吐量指標(biāo),而是看是否命中黑名單來實(shí)現(xiàn)限流。黑名單里面的名單,一方面靠一些內(nèi)部行為分析,比如發(fā)現(xiàn)某個(gè)用戶每秒可以請(qǐng)求千萬次來識(shí)別(就像游戲里面發(fā)現(xiàn)外掛封號(hào))。另一方面就是靠外部風(fēng)控?cái)?shù)據(jù)的導(dǎo)入了。

(4) 風(fēng)控

風(fēng)控在系統(tǒng)防護(hù)中占據(jù)重要地位,然而其建立卻頗為艱難。健全的風(fēng)控體系需要依賴大量數(shù)據(jù),并通過實(shí)際業(yè)務(wù)場(chǎng)景的嚴(yán)苛考驗(yàn)。簡(jiǎn)單來說,風(fēng)控就像繪制用戶畫像,需要收集用戶的靜態(tài)信息,如身份證、IP、設(shè)備號(hào)(如同一設(shè)備或同一IP的多賬戶并行搶購(gòu))、信貸記錄、社保信息、工作信息等多維度信息。同時(shí),還要關(guān)注用戶的動(dòng)態(tài)信息,如是否存在每秒發(fā)起千萬次請(qǐng)求的情況,或者用戶是否只在特定活動(dòng)中才呈現(xiàn)活躍等。

小結(jié)

高并發(fā)的主要挑戰(zhàn)在于瞬時(shí)激增的大量用戶請(qǐng)求需要同時(shí)使用大量的計(jì)算資源。為了解決這一挑戰(zhàn),互聯(lián)網(wǎng)應(yīng)用選用了水平伸縮的發(fā)展路線,即分布式架構(gòu),通過不斷橫向擴(kuò)展集群節(jié)點(diǎn)來增加計(jì)算能力。而我們列舉的方案大部分都直接或間接依賴于分布式架構(gòu)設(shè)計(jì),所以掌握分布式架構(gòu)其實(shí)就等同于掌握高并發(fā)系統(tǒng)設(shè)計(jì)的核心。

優(yōu)秀的架構(gòu)更注重權(quán)衡,而不是追求極端。應(yīng)該從業(yè)務(wù)場(chǎng)景和企業(yè)實(shí)際情況出發(fā),尋找合適且投資回報(bào)率高的方案,而非過度設(shè)計(jì)或追求最極致的解決方案。更不應(yīng)出于恐懼落后或投機(jī)取巧的心態(tài),盲目追求所謂的"最佳實(shí)踐"。

責(zé)任編輯:趙寧寧 來源: Thoughtworks洞見
相關(guān)推薦

2020-10-14 07:20:53

高并發(fā)

2018-09-15 04:59:01

2025-02-20 00:01:00

2021-05-14 14:52:59

高并發(fā)TPSQPS

2021-06-23 06:48:42

秒殺Java電商

2023-11-03 08:32:53

Flask高并發(fā)

2023-05-15 08:12:38

2025-04-08 05:00:00

2021-08-26 08:24:33

高并發(fā)秒殺系統(tǒng)

2019-10-30 16:54:08

golangredis數(shù)據(jù)庫(kù)

2009-06-16 14:43:23

大型網(wǎng)站系統(tǒng)架構(gòu)

2024-10-08 11:21:11

2020-04-13 08:33:39

高并發(fā)秒殺系統(tǒng)

2020-04-22 10:43:49

高并發(fā)數(shù)據(jù)阿里巴巴

2022-03-18 09:11:56

高并發(fā)搶購(gòu)系統(tǒng)架構(gòu)

2024-03-28 08:41:10

高并發(fā).NET異步編程

2025-01-20 00:00:03

高并發(fā)秒殺業(yè)務(wù)

2023-11-27 18:07:05

Go并發(fā)編程

2019-12-03 10:46:07

PHP高并發(fā)架構(gòu)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)