1. 見識(shí)一下京東的秒殺系統(tǒng),真“天花板”!

        共 2512字,需瀏覽 6分鐘

         ·

        2022-01-01 02:14




        京東秒殺是京東最大的營(yíng)銷頻道,近年來(lái)隨著業(yè)務(wù)的高速發(fā)展,頻道商品數(shù)量和用戶流量都呈現(xiàn)出迅猛增長(zhǎng)的態(tài)勢(shì)。


        同時(shí)業(yè)務(wù)方規(guī)劃未來(lái)頻道商品數(shù)量會(huì)增加 5 至 10 倍,對(duì)商品池?cái)U(kuò)容訴求較為強(qiáng)烈,這對(duì)我們現(xiàn)有的系統(tǒng)架構(gòu)提出了挑戰(zhàn)。


        為了應(yīng)對(duì)商品數(shù)量激增引起的風(fēng)險(xiǎn),秒殺后臺(tái)組在年初成立了秒殺商品池?cái)U(kuò)容技術(shù)優(yōu)化專項(xiàng),在 618 前按計(jì)劃完成了千萬(wàn)級(jí)商品池?cái)U(kuò)容的架構(gòu)升級(jí)。本文主要介紹秒殺商品池?cái)U(kuò)容專項(xiàng)的優(yōu)化經(jīng)驗(yàn)。


        京東秒殺頻道業(yè)務(wù)主要包括兩部分:


        • 一部分是頻道核心服務(wù),即直接面向終端用戶提供頻道服務(wù)。

        • 另一部分是維護(hù)秒殺商品池?cái)?shù)據(jù),為商詳、購(gòu)物車等多端提供秒殺商品讀服務(wù),以展示“京東秒殺”的促銷氛圍標(biāo)簽,我們稱為秒殺商品打標(biāo)服務(wù)。



        圖 1:京東秒殺頻道業(yè)務(wù)


        秒殺系統(tǒng)是一個(gè)高并發(fā)大流量系統(tǒng),使用緩存技術(shù)來(lái)提高系統(tǒng)性能。


        在頻道核心服務(wù)的歷史業(yè)務(wù)迭代過(guò)程中,采用了在內(nèi)存中全量緩存商品池?cái)?shù)據(jù)的緩存方案。


        這是因?yàn)轭l道業(yè)務(wù)中存在全量商品按照多維度排序的訴求,同時(shí)在頻道發(fā)展初期商品數(shù)量不多,采用全量緩存的方式內(nèi)存壓力不大,開發(fā)成本較低。


        由于秒殺商品存在時(shí)促銷、庫(kù)存有限的特點(diǎn),對(duì)數(shù)據(jù)更新的實(shí)時(shí)性要求較高,我們通過(guò) ZK 通知的方式實(shí)現(xiàn)商品數(shù)據(jù)更新。


        原系統(tǒng)架構(gòu)如圖 2 所示:



        圖 2:京東秒殺原系統(tǒng)架構(gòu)圖


        秒殺 CMS 系統(tǒng)在商品錄入或更新時(shí),以活動(dòng)的維度將商品數(shù)據(jù)推動(dòng)到 JIMDB(京東內(nèi)部分布式緩存與高速鍵值存儲(chǔ)服務(wù),類似于 Redis)中,同時(shí)通過(guò) ZooKeeper 發(fā)送通知。


        秒殺 SOA 系統(tǒng)監(jiān)聽通知后從 JIMDB 中獲取最新的數(shù)據(jù),更新本地緩存,以提供頻道核心服務(wù)和商品打標(biāo)服務(wù)。



        1

        問題分析


        在以往大促期間,當(dāng)商品池?cái)?shù)量激增時(shí),觀察到系統(tǒng)的堆內(nèi)存消耗過(guò)快,同時(shí) Minor GC 垃圾回收效果有限,Minor GC 回收后堆內(nèi)存低點(diǎn)不斷抬高,堆內(nèi)存呈持續(xù)增長(zhǎng)的態(tài)勢(shì),并且會(huì)規(guī)律性地定期猛增。


        Full GC 較為頻繁,對(duì) CPU 利用率的影響較大,接口性能毛刺現(xiàn)象嚴(yán)重。



        圖 3:系統(tǒng)異常監(jiān)控


        通過(guò) JVM 堆內(nèi)存變化圖可以看到:


        • 堆空間增長(zhǎng)很快,且 Minor GC 無(wú)法回收新增的堆空間。

        • 堆空間呈現(xiàn)有規(guī)律的上升,且會(huì)定期猛增,推測(cè)和定時(shí)任務(wù)有關(guān)。

        • Full GC后,內(nèi)存回收率高,排除內(nèi)存泄漏。

        • Full GC 對(duì) CPU 利用率影響較大。


        頻繁 GC 對(duì)系統(tǒng)的穩(wěn)定性和接口的性能造成嚴(yán)重的影響分析堆對(duì)象增長(zhǎng)情況,通過(guò) jmap -histo 指令在發(fā)生 Full GC 前后打印 JVM 堆中的對(duì)象,如圖 4、圖 5 所示:



        圖 4:發(fā)生 Full GC 前堆內(nèi)存對(duì)象



        圖 5:發(fā)生 Full GC 后堆內(nèi)存對(duì)象


        從 Full GC 前后堆中對(duì)象分布情況分析,以品類秒殺為例,在 Full GC 后堆中不到 100 萬(wàn)商品對(duì)象,占內(nèi)存 125M 左右,和品類秒殺實(shí)際有效商品數(shù)量大致相當(dāng), String 對(duì)象共占約 385M 左右。


        而在發(fā)生 Full GC 前,堆中品類秒殺商品數(shù)量達(dá)到了接近 500 萬(wàn),占用內(nèi)存達(dá)到了 700M,另外 String 對(duì)象占用內(nèi)存達(dá)到 1.2G。


        結(jié)合系統(tǒng)架構(gòu)分析,可以確定是在商品的覆蓋更新過(guò)程中,舊對(duì)象未被回收而不斷進(jìn)入老年代,老年代內(nèi)存占用越來(lái)越高,最終導(dǎo)致堆內(nèi)存不足而產(chǎn)生 Full GC。


        堆對(duì)象中的 String 對(duì)象也是這種更新方式的副產(chǎn)品,這是因?yàn)樯唐窋?shù)據(jù)在 JIMDB 中以 String 方式存儲(chǔ),在更新時(shí)會(huì)從 JIMDB 中拉取到本地反序列化后得到對(duì)象列表。


        可以從圖 6 所示問題代碼中看到產(chǎn)生大 String 對(duì)象的原因:



        圖 6:問題代碼


        對(duì)于上述的全量更新場(chǎng)景,舊對(duì)象和臨時(shí)產(chǎn)生的 String 對(duì)象滿足垃圾回收的條件,為什么沒有在 Minor GC 階段被回收?


        我們知道大多數(shù)情況下,對(duì)象在新生代 Eden 區(qū)中分配,對(duì)象進(jìn)入老年代有以下幾種情況:


        ①大對(duì)象直接進(jìn)入年老代:大對(duì)象即需要大量連續(xù)內(nèi)存空間的 Java 對(duì)象,如長(zhǎng)字符串及數(shù)組。


        大對(duì)象會(huì)導(dǎo)致內(nèi)存剩余空間足夠時(shí),就提前觸發(fā)垃圾收集以獲取足夠的連續(xù)空間來(lái)安置,同時(shí)大對(duì)象的頻繁復(fù)制也會(huì)影響性能。


        虛擬機(jī)提供了一個(gè) -XX:PretenureSizeThreshold 參數(shù),使大于該閾值的對(duì)象直接在老年代分配。為避免臨時(shí) String 對(duì)象直接進(jìn)入老年代的情況,我們顯式關(guān)閉了該功能。


        ②長(zhǎng)期存活的對(duì)象將進(jìn)入年老代:虛擬機(jī)給每個(gè)對(duì)象定義了一個(gè)對(duì)象年齡計(jì)數(shù)器,在對(duì)象在 Eden 創(chuàng)建并經(jīng)過(guò)第一次 Minor GC 后仍然存活,并能被 Suivivor 容納的話,將會(huì)被移動(dòng)到 Survivor 空間,并對(duì)象年齡設(shè)置為 1。


        每經(jīng)歷一次 Minor GC,年齡增加 1 歲,當(dāng)?shù)竭_(dá)閾值時(shí)(可以通過(guò)參數(shù) -XX: MaxTenuringThreshold 設(shè)置,CMS 垃圾回收器默認(rèn)值為 6),將會(huì)晉升老年代。上述分析情況,臨時(shí) String 對(duì)象不會(huì)存活過(guò) 6 次 Minor GC。


        ③動(dòng)態(tài)對(duì)象年齡判定:為了更好地適應(yīng)不同程序內(nèi)存狀況,虛擬機(jī)并不硬性要求對(duì)象年齡達(dá)到 MaxTenuringThreshold 才能晉升老年代。


        如果在 Survivor 空間中小于等于某個(gè)年齡所有對(duì)象大小的總和大于 Survivor 空間的一半,年齡大于或等于該年齡的對(duì)象就可以直接進(jìn)入年老代。


        通過(guò)上述分析,我們發(fā)現(xiàn)臨時(shí) String 對(duì)象最有可能觸發(fā)了動(dòng)態(tài)對(duì)象年齡判定機(jī)制而進(jìn)入老年代。


        打印虛擬機(jī) GC 信息,并添加-XX: +PrintTenuringDistribution參數(shù)來(lái)打印發(fā)生 GC 時(shí)新生代的對(duì)象年齡信息,得到圖 7 所示 GC 日志信息:



        圖 7:GC 日志


        從 GC 日志可以看到,Survivor 空間大小為 358M,Survivor 區(qū)的目標(biāo)使用率默認(rèn)是 50%,Desired Survivor size 是 179M,age <= 2 的對(duì)象大小總和為 269M。


        因此雖然設(shè)定的晉升閾值是 6,虛擬機(jī)動(dòng)態(tài)計(jì)算晉升閾值為 2,最終導(dǎo)致 age 大于等于 2 的對(duì)象都進(jìn)入老年代。


        我們嘗試從優(yōu)化 JVM 參數(shù)的方式解決問題,效果并不理想。做過(guò)的嘗試有:


        增大年輕代的空間來(lái)減少對(duì)象進(jìn)入老年代,結(jié)果適得其反,STW 更加頻繁,CPU 利用率波動(dòng)也更大。


        改用 G1 垃圾收集器,效果不明顯,CPU 利用率波動(dòng)也更大。


        顯式設(shè)置晉升老年代的閾值(MaxTenuringThreshold),試圖推遲對(duì)象進(jìn)入老年代的速度,無(wú)任何效果。


        上述問題分析的結(jié)論對(duì)我們的啟示是:如果在新生代中頻繁產(chǎn)生朝生夕死的大對(duì)象,會(huì)觸發(fā)虛擬機(jī)的動(dòng)態(tài)對(duì)象年齡判定機(jī)制,降低對(duì)象進(jìn)入老年代的門檻,導(dǎo)致堆內(nèi)存增長(zhǎng)過(guò)快。


        ?

        2

        優(yōu)化方案


        ①雙緩存區(qū)定時(shí)散列更新


        通過(guò)上面的分析可以發(fā)現(xiàn),為了防止堆內(nèi)存增長(zhǎng)過(guò)快,需要控制商品數(shù)據(jù)更新的粒度和頻次。


        原有的商品更新方案是商品數(shù)據(jù)按照活動(dòng)的維度全量覆蓋更新,每個(gè)商品的狀態(tài)變化都會(huì)觸發(fā)更新操作。


        我們希望數(shù)據(jù)更新能控制在更小的范圍,同時(shí)能夠控制數(shù)據(jù)更新的頻率,最終設(shè)計(jì)出雙緩存區(qū)定時(shí)散列更新方案,如圖 8 所示。



        圖 8:雙緩存區(qū)定時(shí)散列更新示意圖


        該方案的實(shí)現(xiàn)是將活動(dòng)下的商品以 SKU 維度散列到不同的桶中,更新的操作以桶的粒度進(jìn)行。


        同時(shí)為了控制數(shù)據(jù)更新的頻率,我們?cè)?SOA 端設(shè)計(jì)了雙緩存區(qū)定時(shí)切量的方式。


        在 CMS 商品數(shù)據(jù)更新時(shí),會(huì)映射到需要更新的桶,并實(shí)時(shí)通知 SOA 端;在 SOA 端收到 ZK 通知后,會(huì)在讀緩存區(qū)標(biāo)記需要更新的桶,但不會(huì)實(shí)時(shí)的更新數(shù)據(jù)。


        在達(dá)到定時(shí)時(shí)間后,會(huì)自動(dòng)切換讀寫緩存區(qū),此時(shí)會(huì)讀取讀緩存區(qū)中標(biāo)記的待更新桶,從 JIMDB 中獲取桶對(duì)應(yīng)的商品列表,完成數(shù)據(jù)的細(xì)粒度分段更新。


        該方案散列份數(shù)和定時(shí)時(shí)間可以根據(jù)具體業(yè)務(wù)情況進(jìn)行調(diào)整,在性能和實(shí)時(shí)性上取得平衡,在上線后取得了較好的優(yōu)化效果。


        ②引入本地 LRU 緩存


        雙緩存區(qū)定時(shí)散列更新的方案雖然在系統(tǒng)性能上得到了提升,但依然無(wú)法支持千萬(wàn)級(jí)商品的擴(kuò)容。


        為了徹底擺脫機(jī)器內(nèi)存對(duì)商品池容量的限制,我們啟動(dòng)了秒殺架構(gòu)的全面升級(jí),核心思路是引入本地 LRU 緩存組件,實(shí)現(xiàn)冷數(shù)據(jù)淘汰,以控制內(nèi)存中緩存商品的總數(shù)量在安全區(qū)間。


        系統(tǒng)拆分:原系統(tǒng)存在的問題是,頻道核心服務(wù)和商品打標(biāo)服務(wù)共用相同的基礎(chǔ)數(shù)據(jù),存在系統(tǒng)耦合的問題。


        從商品池角度分析,頻道核心服務(wù)商品池是秒殺商品池的子集。從業(yè)務(wù)角度分析,頻道核心服務(wù)業(yè)務(wù)邏輯復(fù)雜,調(diào)用鏈路長(zhǎng),響應(yīng)時(shí)間長(zhǎng),商品打標(biāo)服務(wù)邏輯簡(jiǎn)單,調(diào)用鏈路短,響應(yīng)時(shí)間短。


        將頻道核心服務(wù)和商品打標(biāo)服務(wù)進(jìn)行拆分,獨(dú)立部署,實(shí)現(xiàn)資源隔離,這樣可以根據(jù)業(yè)務(wù)特點(diǎn)做針對(duì)性優(yōu)化。


        頻道核心服務(wù)可以減少內(nèi)存中商品緩存的數(shù)量,商品打標(biāo)服務(wù)可以升級(jí)商品緩存方案,另外也可以規(guī)避架構(gòu)升級(jí)過(guò)程中對(duì)頻道核心服務(wù)的影響。



        圖 9:系統(tǒng)拆分


        緩存方案優(yōu)化:頻道核心服務(wù)歷史邏輯復(fù)雜,且直接面向終端用戶,升級(jí)難度大。


        在擴(kuò)容專項(xiàng)一期中的主要優(yōu)化點(diǎn)是拆分出頻道核心服務(wù)商品池,去除非頻道展示商品,以減少商品緩存數(shù)量。一期優(yōu)化主要聚焦于秒殺打標(biāo)服務(wù)的緩存方案升級(jí)。


        在原有的系統(tǒng)架構(gòu)中秒殺商品池全量緩存在內(nèi)存中,這會(huì)導(dǎo)致商品數(shù)量激增時(shí),JVM 堆內(nèi)存資源緊張,商品池的容量受到限制,且無(wú)法水平擴(kuò)容。


        商品以活動(dòng)的維度進(jìn)行存儲(chǔ)和更新,會(huì)導(dǎo)致大 key 的問題,在進(jìn)行覆蓋更新時(shí)會(huì)在內(nèi)存中產(chǎn)生臨時(shí)的大對(duì)象,不利于 JVM 垃圾回收表現(xiàn)。



        圖 10:緩存方案升級(jí)


        對(duì)于拆封后的商品打標(biāo)服務(wù),緩存方案優(yōu)化的總體思路是實(shí)現(xiàn)冷熱數(shù)據(jù)的拆分。


        升級(jí)后的商品打標(biāo)服務(wù)不再使用本地全量緩存,而是使用 JIMDB 全量緩存+本地 LRU 緩存組件的方式。


        對(duì)緩存組件的要求是在緩存數(shù)據(jù)達(dá)到預(yù)設(shè)商品數(shù)量上限時(shí),實(shí)現(xiàn)冷數(shù)據(jù)的清退,同時(shí)具有較高的緩存命中率和讀寫性能。


        在對(duì)比常用的緩存框架 Caffeine 和 Guava Cache 后最終采用 Caffeine 緩存。


        其優(yōu)勢(shì)有:


        • 性能更優(yōu)。Caffeine 的讀寫性能顯著優(yōu)于 Guava, 這是由于 Guava 中讀寫操作夾雜著過(guò)期時(shí)間的處理,一次 put 操作中有可能會(huì)觸發(fā)淘汰操作,所以其讀寫性能會(huì)受到一定影響。

        • 而 Caffeine 對(duì)這些事件的操作是異步的,將事件提交至隊(duì)列,通過(guò)默認(rèn)的 ForkJoinPool.commonPool() 或自己配置的線程池,進(jìn)行取隊(duì)列操作,再進(jìn)行異步淘汰、過(guò)期操作。

        • 高命中率,低內(nèi)存占用。Guava 使用分段 LRU 算法,而 Caffeine 使用了一種結(jié)合 LRU、LFU 優(yōu)點(diǎn)的算法:W-TinyLFU,可以使用較少的資源來(lái)記錄訪問頻次,同時(shí)能夠解決稀疏突發(fā)訪問元素的問題。


        升級(jí)后的架構(gòu)圖如圖 11 所示:



        圖 11:升級(jí)后架構(gòu)圖


        頻道核心服務(wù)和商品打標(biāo)服務(wù)獨(dú)立部署,資源隔離。秒殺 CMS 在商品錄入和更新時(shí),以 SKU 維度寫入 JIMDB 中組成全量秒殺商品池。


        商品打標(biāo)服務(wù)通過(guò) Caffeine 緩存的方式,設(shè)置寫入寫入 30s 過(guò)期,最大緩存 200w 商品數(shù)據(jù),實(shí)現(xiàn)熱數(shù)據(jù)緩存,過(guò)期數(shù)據(jù)和冷數(shù)據(jù)的淘汰。


        ③引入布隆過(guò)濾器


        在非秒殺 SKU 查詢處理上,為了避免緩存穿透問題(即單個(gè)無(wú)效商品的高頻次查詢,如果本地緩存中沒有則每次請(qǐng)求都會(huì)訪問到 JIMDB),我們對(duì)于非秒殺商品的查詢結(jié)果,在本地緩存中存儲(chǔ)一個(gè)空值標(biāo)識(shí),避免無(wú)效 SKU 請(qǐng)求每次都訪問到 JIMDB。


        商詳、購(gòu)物車等渠道商品池?cái)?shù)量比秒殺商品池高幾個(gè)數(shù)量級(jí),秒殺查詢服務(wù)請(qǐng)求 SKU 中存在大量的非秒殺商品,這會(huì)導(dǎo)致本地緩存的命中率降低,同時(shí)帶來(lái)緩存雪崩的風(fēng)險(xiǎn)。


        為了攔截大量非秒殺 SKU 的請(qǐng)求,我們引入過(guò)濾器機(jī)制。在本地過(guò)濾器的選擇上,我們嘗試使用所有有效商品 SkuId 組成的 Set 集合來(lái)生成本地過(guò)濾器,上線后觀察到本地過(guò)濾器數(shù)據(jù)更新時(shí)會(huì)產(chǎn)生性能波動(dòng)。







        簡(jiǎn)介:《秒殺系統(tǒng)架構(gòu)優(yōu)化思路》 上周參加Qcon,有個(gè)兄弟分享秒殺系統(tǒng)的優(yōu)化,其觀點(diǎn)有些贊同,大部分觀點(diǎn)卻并不同意,結(jié)合自己的經(jīng)驗(yàn),談?wù)勛约旱囊恍┛捶āR?、為什么難 秒殺系統(tǒng)難做的原因:庫(kù)存只有一份,所有人會(huì)在集中的時(shí)間讀和寫這些數(shù)據(jù)。例如小米手機(jī)每周二的秒殺,可能手機(jī)只有1萬(wàn)部,但瞬時(shí)進(jìn)入的流量可能是幾百幾千萬(wàn)。又例如12306搶票,亦與秒殺類似,瞬時(shí)流量更甚。

        《秒殺系統(tǒng)架構(gòu)優(yōu)化思路》


        上周參加Qcon,有個(gè)兄弟分享秒殺系統(tǒng)的優(yōu)化,其觀點(diǎn)有些贊同,大部分觀點(diǎn)卻并不同意,結(jié)合自己的經(jīng)驗(yàn),談?wù)勛约旱囊恍┛捶ā?/p>


        一、為什么難

        秒殺系統(tǒng)難做的原因:庫(kù)存只有一份,所有人會(huì)在集中的時(shí)間讀和寫這些數(shù)據(jù)。

        例如小米手機(jī)每周二的秒殺,可能手機(jī)只有1萬(wàn)部,但瞬時(shí)進(jìn)入的流量可能是幾百幾千萬(wàn)。

        又例如12306搶票,亦與秒殺類似,瞬時(shí)流量更甚。


        二、常見架構(gòu)


        流量到了億級(jí)別,常見站點(diǎn)架構(gòu)如上:

        1)瀏覽器端,最上層,會(huì)執(zhí)行到一些JS代碼

        2)站點(diǎn)層,這一層會(huì)訪問后端數(shù)據(jù),拼html頁(yè)面返回給瀏覽器

        3)服務(wù)層,向上游屏蔽底層數(shù)據(jù)細(xì)節(jié)

        4)數(shù)據(jù)層,最終的庫(kù)存是存在這里的,mysql是一個(gè)典型


        三、優(yōu)化方向

        1)將請(qǐng)求盡量攔截在系統(tǒng)上游:傳統(tǒng)秒殺系統(tǒng)之所以掛,請(qǐng)求都?jí)旱沽撕蠖藬?shù)據(jù)層,數(shù)據(jù)讀寫鎖沖突嚴(yán)重,并發(fā)高響應(yīng)慢,幾乎所有請(qǐng)求都超時(shí),流量雖大,下單成功的有效流量甚小【一趟火車其實(shí)只有2000張票,200w個(gè)人來(lái)買,基本沒有人能買成功,請(qǐng)求有效率為0】

        2)充分利用緩存:這是一個(gè)典型的讀多寫少的應(yīng)用場(chǎng)景【一趟火車其實(shí)只有2000張票,200w個(gè)人來(lái)買,最多2000個(gè)人下單成功,其他人都是查詢庫(kù)存,寫比例只有0.1%,讀比例占99.9%】,非常適合使用緩存


        四、優(yōu)化細(xì)節(jié)

        4.1)瀏覽器層請(qǐng)求攔截

        點(diǎn)擊了“查詢”按鈕之后,系統(tǒng)那個(gè)卡呀,進(jìn)度條漲的慢呀,作為用戶,我會(huì)不自覺的再去點(diǎn)擊“查詢”,繼續(xù)點(diǎn),繼續(xù)點(diǎn),點(diǎn)點(diǎn)點(diǎn)。。。有用么?平白無(wú)故的增加了系統(tǒng)負(fù)載(一個(gè)用戶點(diǎn)5次,80%的請(qǐng)求是這么多出來(lái)的),怎么整?

        a)產(chǎn)品層面,用戶點(diǎn)擊“查詢”或者“購(gòu)票”后,按鈕置灰,禁止用戶重復(fù)提交請(qǐng)求

        b)JS層面,限制用戶在x秒之內(nèi)只能提交一次請(qǐng)求

        如此限流,80%流量已攔。


        4.2)站點(diǎn)層請(qǐng)求攔截與頁(yè)面緩存

        瀏覽器層的請(qǐng)求攔截,只能攔住小白用戶(不過(guò)這是99%的用戶喲),高端的程序員根本不吃這一套,寫個(gè)for循環(huán),直接調(diào)用你后端的http請(qǐng)求,怎么整?

        a)同一個(gè)uid,限制訪問頻度,做頁(yè)面緩存,x秒內(nèi)到達(dá)站點(diǎn)層的請(qǐng)求,均返回同一頁(yè)面

        b)同一個(gè)item的查詢,例如手機(jī)車次,做頁(yè)面緩存,x秒內(nèi)到達(dá)站點(diǎn)層的請(qǐng)求,均返回同一頁(yè)面

        如此限流,又有99%的流量會(huì)被攔截在站點(diǎn)層


        4.3)服務(wù)層請(qǐng)求攔截與數(shù)據(jù)緩存

        站點(diǎn)層的請(qǐng)求攔截,只能攔住普通程序員,高級(jí)黑客,假設(shè)他控制了10w臺(tái)肉雞(并且假設(shè)買票不需要實(shí)名認(rèn)證),這下uid的限制不行了吧?怎么整?

        a)大哥,我是服務(wù)層,我清楚的知道小米只有1萬(wàn)部手機(jī),我清楚的知道一列火車只有2000張車票,我透10w個(gè)請(qǐng)求去數(shù)據(jù)庫(kù)有什么意義呢?對(duì)于寫請(qǐng)求,做請(qǐng)求隊(duì)列,每次只透有限的寫請(qǐng)求去數(shù)據(jù)層,如果均成功再放下一批,如果庫(kù)存不夠則隊(duì)列里的寫請(qǐng)求全部返回“已售完”

        b)對(duì)于讀請(qǐng)求,還要我說(shuō)么?cache抗,不管是memcached還是redis,單機(jī)抗個(gè)每秒10w應(yīng)該都是沒什么問題的

        如此限流,只有非常少的寫請(qǐng)求,和非常少的讀緩存mis的請(qǐng)求會(huì)透到數(shù)據(jù)層去,又有99.9%的請(qǐng)求被攔住了


        4.4)數(shù)據(jù)層閑庭信步

        到了數(shù)據(jù)這一層,幾乎就沒有什么請(qǐng)求了,單機(jī)也能扛得住,還是那句話,庫(kù)存是有限的,小米的產(chǎn)能有限,透這么多請(qǐng)求來(lái)數(shù)據(jù)庫(kù)沒有意義。


        五、總結(jié)

        沒什么總結(jié)了,上文應(yīng)該描述的非常清楚了,對(duì)于秒殺系統(tǒng),再次重復(fù)下筆者的兩個(gè)架構(gòu)優(yōu)化思路:

        1)盡量將請(qǐng)求攔截在系統(tǒng)上游

        2)讀多寫少的常用多使用緩存


        你一聽,完了呀,這我們的服務(wù)器哪里頂?shù)米“?!說(shuō)真的直接打DB肯定掛。

        是吧,秒殺的特點(diǎn)就是這樣時(shí)間極短、 瞬間用戶量大。正常的店鋪營(yíng)銷都是用極低的價(jià)格配合上短信、APP的精準(zhǔn)推送,

        吸引特別多的用戶來(lái)參與這場(chǎng)秒殺,爽了商家苦了開發(fā)呀。秒殺大家都知道如果真的營(yíng)銷到位,價(jià)格誘人,幾十萬(wàn)的流量我覺得完全不是問題,

        那單機(jī)的Redis我感覺3-4W的QPS還是能頂?shù)米〉?,但是再高了就沒辦法了,那這個(gè)數(shù)據(jù)隨便搞個(gè)熱銷商品的秒殺可能都不止了。

        大量的請(qǐng)求進(jìn)來(lái),我們需要考慮的點(diǎn)就很多了,緩存雪崩,緩存擊穿,緩存穿透這些我之前提到的點(diǎn)都是有可能發(fā)生的,出現(xiàn)問題打掛DB那就很難受了,

        活動(dòng)失敗用戶體驗(yàn)差,活動(dòng)人氣沒了,最后背鍋的還是開發(fā)。

        超賣:但凡是個(gè)秒殺,都怕超賣,我這里舉例的只是尿不濕,要是換成100個(gè)華為MatePro3

        0,商家的預(yù)算經(jīng)費(fèi)賣100個(gè)可以賺點(diǎn)還可以造勢(shì),結(jié)果你寫錯(cuò)程序多賣出去200個(gè),你不發(fā)貨用戶投訴你,平臺(tái)封你店,你發(fā)貨就血虧,你怎么辦?(沒事

        看了敖丙的文章直接不怕)那最后只能殺個(gè)開發(fā)祭天解氣了,秒殺的價(jià)格本來(lái)就低了,基本上都是不怎么賺錢的,超賣了就恐怖了呀,所以超賣也是很關(guān)鍵的

        一個(gè)點(diǎn)。惡意請(qǐng)求:你這么低的價(jià)格,假如我搶到了,我轉(zhuǎn)手賣掉我不是血賺?就算我不賣我也不虧啊,那用戶知道,你知道,別的別有用心的人(黑客、黃?!┛隙ㄒ仓赖?。那簡(jiǎn)單啊,我知道你什么時(shí)候搶,我搞個(gè)幾十臺(tái)機(jī)器搞點(diǎn)腳本,我也模擬出來(lái)十幾萬(wàn)個(gè)人左右的請(qǐng)求,那我是不是意味著我基本上有80%的成功率了。真實(shí)情況可能遠(yuǎn)遠(yuǎn)不止,因?yàn)闄C(jī)器請(qǐng)求的速度比人的手速往往快太多了,在貴州的敖丙我每年回家搶高鐵票都是秒光的,我也不知道有沒有黃牛的功勞,我要Diss你,黃牛。杰倫演唱會(huì)門票搶不到,我也Diss你。Tip:科普下,小道消息了解到的,黃牛的搶票系統(tǒng),比國(guó)內(nèi)很多小公司的系統(tǒng)還吊很多,架構(gòu)設(shè)計(jì)都是頂級(jí)的,我用頂配的服務(wù)加上頂配的架構(gòu)設(shè)計(jì),你還想看演唱會(huì)?還想回家?不過(guò)不用黃牛我回家都難,我們?cè)瀑F川跟我一樣要回家過(guò)年的仔太多了555!點(diǎn)我領(lǐng)取一線大廠面試資料和簡(jiǎn)歷模板鏈接暴露:前面幾個(gè)問題大家可能都很好理解,一看到這個(gè)有的小伙伴可能會(huì)比較疑惑,啥是鏈接暴露呀?相信是個(gè)開發(fā)同學(xué)都對(duì)這個(gè)畫面一點(diǎn)都不陌生吧,懂點(diǎn)行的仔都可以打開谷歌的開發(fā)者模式,然后看看你的網(wǎng)頁(yè)代碼,有的就有URL,但是我寫VUE的時(shí)候是事件觸發(fā)然后去調(diào)用文件里面的接口看源碼看不到,但是我可以點(diǎn)擊一下查看你的請(qǐng)求地址啊,不過(guò)你好像可以對(duì)按鈕在秒殺前置灰。不管怎么樣子都有危險(xiǎn),撇開外面的所有的東西你都擋住了,你賣這個(gè)東西實(shí)在便宜得過(guò)分,有誘惑力,你能保證開發(fā)不動(dòng)心?開發(fā)知道地址,在秒殺的時(shí)候自己提前請(qǐng)求。。。(開發(fā):怎么TM又是我)數(shù)據(jù)庫(kù):每秒上萬(wàn)甚至十幾萬(wàn)的QPS(每秒請(qǐng)求數(shù))直接打到數(shù)據(jù)庫(kù),基本上都要把庫(kù)打掛掉,而且你服務(wù)不單單是做秒殺的還涉及其他的業(yè)務(wù),你沒做降級(jí)、限流、熔斷啥的,別的一起掛,小公司的話可能全站崩潰404。反正不管你秒殺怎么掛,你別把別的搞掛了對(duì)吧,搞掛了就不是殺一個(gè)程序員能搞定的。程序員:我TM好難啊!問題都列出來(lái)了,那怎么設(shè)計(jì),怎么解決這些問題就是接下去要考慮的了,我們對(duì)癥下藥。服務(wù)單一職責(zé):設(shè)計(jì)個(gè)能抗住高并發(fā)的系統(tǒng),我覺得還是得單一職責(zé)。什么意思呢,大家都知道現(xiàn)在設(shè)計(jì)都是微服務(wù)的設(shè)計(jì)思想,然后再用分布式的部署方式也就是我們下單是有個(gè)訂單服務(wù),用戶登錄管理等有個(gè)用戶服務(wù)等等,那為啥我們不給秒殺也開個(gè)服務(wù),我們把秒殺的代碼業(yè)務(wù)邏輯放一起。單獨(dú)給他建立一個(gè)數(shù)據(jù)庫(kù),現(xiàn)在的互聯(lián)網(wǎng)架構(gòu)部署都是分庫(kù)的,一樣的就是訂單服務(wù)對(duì)應(yīng)訂單庫(kù),秒殺我們也給他建立自己的秒殺庫(kù)。至于表就看大家怎么設(shè)計(jì)了,該設(shè)置索引的地方還是要設(shè)置索引的,建完后記得用explain看看SQL的執(zhí)行計(jì)劃。(不了解的小伙伴也沒事,MySQL章節(jié)我會(huì)說(shuō)的)單一職責(zé)的好處就是就算秒殺沒抗住,秒殺庫(kù)崩了,服務(wù)掛了,也不會(huì)影響到其他的服務(wù)。(強(qiáng)行高可用)秒殺鏈接加鹽:我們上面說(shuō)了鏈接要是提前暴露出去可能有人直接訪問url就提前秒殺了,那又有小伙伴要說(shuō)了我做個(gè)時(shí)間的校驗(yàn)就好了呀,那我告訴你,知道鏈接的地址比起頁(yè)面人工點(diǎn)擊的還是有很大優(yōu)勢(shì)。我知道url了,那我通過(guò)程序不斷獲取最新的北京時(shí)間,可以達(dá)到毫秒級(jí)別的,我就在00毫秒的時(shí)候請(qǐng)求,我敢說(shuō)絕對(duì)比你人工點(diǎn)的成功率大太多了,而且我可以一毫秒發(fā)送N次請(qǐng)求,搞不好你賣100個(gè)產(chǎn)品我全拿了。那這種情況怎么避免?簡(jiǎn)單,把URL動(dòng)態(tài)化,就連寫代碼的人都不知道,你就通過(guò)MD5之類的加密算法加密隨機(jī)的字符串去做url,然后通過(guò)前端代碼獲取url后臺(tái)校驗(yàn)才能通過(guò)。暖男我呢,又準(zhǔn)備了一個(gè)簡(jiǎn)單的url加密給大家嘗嘗鮮,還不點(diǎn)個(gè)贊?Redis集群:之前不是說(shuō)單機(jī)的Redis頂不住嘛,那簡(jiǎn)單多找?guī)讉€(gè)兄弟啊,秒殺本來(lái)就是讀多寫少,那你們是不是瞬間想起來(lái)我之前跟你們提到過(guò)的,Redis集群,主從同步、讀寫分離,我們還搞點(diǎn)哨兵,開啟持久化直接無(wú)敵高可用!Nginx:Nginx大家想必都不陌生了吧,這玩意是高性能的web服務(wù)器,并發(fā)也隨便頂幾萬(wàn)不是夢(mèng),但是我們的Tomcat只能頂幾百的并發(fā)呀,那簡(jiǎn)單呀負(fù)載均衡嘛,一臺(tái)服務(wù)幾百,那就多搞點(diǎn),在秒殺的時(shí)候多租點(diǎn)流量機(jī)。Tip:據(jù)我所知國(guó)內(nèi)某大廠就是在去年春節(jié)活動(dòng)期間租光了亞洲所有的服務(wù)器,小公司也很喜歡在雙十一期間買流量機(jī)來(lái)頂住壓力。這樣一對(duì)比是不是覺得你的集群能頂很多了。惡意請(qǐng)求攔截也需要用到它,一般單個(gè)用戶請(qǐng)求次數(shù)太夸張,不像人為的請(qǐng)求在網(wǎng)關(guān)那一層就得攔截掉了,不然請(qǐng)求多了他搶不搶得到是一回事,服務(wù)器壓力上去了,可能占用網(wǎng)絡(luò)帶寬或者把服務(wù)器打崩、緩存擊穿等等。資源靜態(tài)化:秒殺一般都是特定的商品還有頁(yè)面模板,現(xiàn)在一般都是前后端分離的,所以頁(yè)面一般都是不會(huì)經(jīng)過(guò)后端的,但是前端也要自己的服務(wù)器啊,那就把能提前放入cdn服務(wù)器的東西都放進(jìn)去,反正把所有能提升效率的步驟都做一下,減少真正秒殺時(shí)候服務(wù)器的壓力。點(diǎn)我領(lǐng)取一線大廠面試資料和簡(jiǎn)歷模板按鈕控制:大家有沒有發(fā)現(xiàn)沒到秒殺前,一般按鈕都是置灰的,只有時(shí)間到了,才能點(diǎn)擊。這是因?yàn)榕麓蠹以跁r(shí)間快到的最后幾秒秒瘋狂請(qǐng)求服務(wù)器,然后還沒到秒殺的時(shí)候基本上服務(wù)器就掛了。這個(gè)時(shí)候就需要前端的配合,定時(shí)去請(qǐng)求你的后端服務(wù)器,獲取最新的北京時(shí)間,到時(shí)間點(diǎn)再給按鈕可用狀態(tài)。按鈕可以點(diǎn)擊之后也得給他置灰?guī)酌?,不然他一樣在開始之后一直點(diǎn)的。你敢說(shuō)你們秒殺的時(shí)候不是這樣的?限流:限流這里我覺得應(yīng)該分為前端限流和后端限流。前端限流:這個(gè)很簡(jiǎn)單,一般秒殺不會(huì)讓你一直點(diǎn)的,一般都是點(diǎn)擊一下或者兩下然后幾秒之后才可以繼續(xù)點(diǎn)擊,這也是保護(hù)服務(wù)器的一種手段。后端限流:秒殺的時(shí)候肯定是涉及到后續(xù)的訂單生成和支付等操作,但是都只是成功的幸運(yùn)兒才會(huì)走到那一步,那一旦100個(gè)產(chǎn)品賣光了,return了一個(gè)false,前端直接秒殺結(jié)束,然后你后端也關(guān)閉后續(xù)無(wú)效請(qǐng)求的介入了。Tip:真正的限流還會(huì)有限流組件的加入例如:阿里的Sentinel、Hystrix等。我這里就不展開了,就說(shuō)一下物理的限流。庫(kù)存預(yù)熱:秒殺的本質(zhì),就是對(duì)庫(kù)存的搶奪,每個(gè)秒殺的用戶來(lái)你都去數(shù)據(jù)庫(kù)查詢庫(kù)存校驗(yàn)庫(kù)存,然后扣減庫(kù)存,撇開性能因數(shù),你不覺得這樣好繁瑣,對(duì)業(yè)務(wù)開發(fā)人員都不友好,而且數(shù)據(jù)庫(kù)頂不住啊。開發(fā):你tm總算為我著想一次了。那怎么辦?我們都知道數(shù)據(jù)庫(kù)頂不住但是他的兄弟非關(guān)系型的數(shù)據(jù)庫(kù)Redis能頂??!那不簡(jiǎn)單了,我們要開始秒殺前你通過(guò)定時(shí)任務(wù)或者運(yùn)維同學(xué)提前把商品的庫(kù)存加載到Redis中去,讓整個(gè)流程都在Redis里面去做,然后等秒殺介紹了,再異步的去修改庫(kù)存就好了。但是用了Redis就有一個(gè)問題了,我們上面說(shuō)了我們采用主從,就是我們會(huì)去讀取庫(kù)存然后再判斷然后有庫(kù)存才去減庫(kù)存,正常情況沒問題,但是高并發(fā)的情況問題就很大了。這里我就不畫圖了,我本來(lái)想畫圖的,想了半天我覺得語(yǔ)言可能更好表達(dá)一點(diǎn)。多品幾遍?。。【捅热绗F(xiàn)在庫(kù)存只剩下1個(gè)了,我們高并發(fā)嘛,4個(gè)服務(wù)器一起查詢了發(fā)現(xiàn)都是還有1個(gè),那大家都覺得是自己搶到了,就都去扣庫(kù)存,那結(jié)果就變成了-3,是的只有一個(gè)是真的搶到了,別的都是超賣的。咋辦?Lua:之前的文章就簡(jiǎn)單的提到了他,我今天就多一定點(diǎn)篇幅說(shuō)一下吧。Lua 腳本功能是 Reids在 2.6 版本的最大亮點(diǎn), 通過(guò)內(nèi)嵌對(duì) Lua 環(huán)境的支持, Redis 解決了長(zhǎng)久以來(lái)不能高效地處理 CAS (check-and-set)命令的缺點(diǎn), 并且可以通過(guò)組合使用多個(gè)命令, 輕松實(shí)現(xiàn)以前很難實(shí)現(xiàn)或者不能高效實(shí)現(xiàn)的模式。Lua腳本是類似Redis事務(wù),有一定的原子性,不會(huì)被其他命令插隊(duì),可以完成一些Redis事務(wù)性的操作。這點(diǎn)是關(guān)鍵。知道原理了,我們就寫一個(gè)腳本把判斷庫(kù)存扣減庫(kù)存的操作都寫在一個(gè)腳本丟給Redis去做,那到0了后面的都Return False了是吧,一個(gè)失敗了你修改一個(gè)開關(guān),直接擋住所有的請(qǐng)求,然后再做后面的事情嘛。限流&降級(jí)&熔斷&隔離:這個(gè)為啥要做呢,不怕一萬(wàn)就怕萬(wàn)一,萬(wàn)一你真的頂不住了,限流,頂不住就擋一部分出去但是不能說(shuō)不行,降級(jí),降級(jí)了還是被打掛了,熔斷,至少不要影響別的系統(tǒng),隔離,你本身就獨(dú)立的,但是你會(huì)調(diào)用其他的系統(tǒng)嘛,你快不行了你別拖累兄弟們啊。


        分析發(fā)現(xiàn)這種方式空間復(fù)雜度高,內(nèi)存占用比較高。過(guò)濾器優(yōu)化為布隆過(guò)濾器后,內(nèi)存占用降低,性能得到進(jìn)一步提升。



        3

        優(yōu)化效果


        在完成架構(gòu)升級(jí)后,經(jīng)過(guò)單機(jī)壓測(cè)、灰度驗(yàn)證、灰度上線、全量壓測(cè)等過(guò)程嚴(yán)格驗(yàn)證了新系統(tǒng)的性能和結(jié)果準(zhǔn)確性,在 618 大促前新系統(tǒng)全量平穩(wěn)上線。


        從近年來(lái)大促期間系統(tǒng)表現(xiàn)來(lái)看,優(yōu)化效果顯著,如圖 12、圖 13 所示,主要體現(xiàn)在以下幾個(gè)方面。



        圖 12:大促性能表現(xiàn)對(duì)比


        業(yè)務(wù)支撐:秒殺商品池?cái)?shù)量持續(xù)增長(zhǎng),由于架構(gòu)的調(diào)整全量商品緩存在 JIMDB,新系統(tǒng)支持水平擴(kuò)容,后續(xù)可支持更高數(shù)量級(jí)的商品,滿足業(yè)務(wù)的長(zhǎng)期規(guī)劃。


        性能優(yōu)化:大促期間打標(biāo)服務(wù)的接口 tp999 持續(xù)下降,618 大促接口性能提升 90%,同時(shí)從接口性能對(duì)比上看,接口性能的毛刺現(xiàn)象得到解決。


        穩(wěn)定性提升:GC 頻率持續(xù)下降,系統(tǒng)穩(wěn)定性得到提高。



        圖 13:接口性能監(jiān)控對(duì)比


        ?

        4

        總結(jié)


        本次秒殺商品池?cái)U(kuò)容優(yōu)化專項(xiàng)通過(guò)優(yōu)化商品更新方式、系統(tǒng)拆分、優(yōu)化緩存方案等方式,實(shí)現(xiàn)了系統(tǒng)架構(gòu)升級(jí),提升了頻道的商品容量和性能,達(dá)到了預(yù)設(shè)目標(biāo)。


        ??如果喜歡本篇,歡迎「點(diǎn)贊、在看?」,下次見~

        瀏覽 137
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. 无码另类| 黑人玩弄人妻一区二区三区影院 | 久久久久综合 | 香蕉久久国产AV一区二区 | 91无码电影 |