1. 程式人生 > >多 “維” 優化——前端高並發策略的更深層思考

多 “維” 優化——前端高並發策略的更深層思考

經歷 耗時 保護 這樣的 大致 str 頁面請求 本質 target

作者:徐嘉偉,騰訊web前端開發 高級工程師

商業轉載請聯系騰訊WeTest獲得授權,非商業轉載請註明出處。


WeTest 導讀

一項指標的變好,總少不了相應優化策略的實施。優化並不是簡單的一蹴而就,而是個不斷叠代與推翻的過程。更深層的優化方案,往往是在某種思維策略之下,對問題場景和基本策略優缺的深刻理解後做出的當下最優的權衡結果。本文筆者從前端高並發優化這一具體點出發,逐步向大家闡述筆者在優化的“術”之上思維層面的一些思考。希望能給各位帶來共鳴和感悟。

背景:
之所以會以前端高並發這一主題入手,一來是本人曾負責過一些超高並發量的業務(手Q紅包),在這方面算是有些經驗。二來是相對於業務功能優化這類光前端層面的邏輯就涉及產品、設計等多方人員合作討論而完成的優化(即邏輯本身並非純出自前端人員的腦子),前端高並發這種前端層面邏輯純由前端人員全把控的優化,或許作為前端的我,能得出來的思考觀點會更深刻和更通用一些。

一、普遍的優化思路

說到優化,大家在收到“優化指標”任務的時候。通常會做兩件事情——分析“優化指標”對應的痛點、尋找解決痛點的技術方案並施行。那這樣是否就足夠了呢?我的答案是否定的。在我的認知裏這只是第一層的優化,雖然在結果上往往我們使用更優的技術後確實可以達到更好的優化效果,但卻又不那麽完美,優化效果還可以做得更好。那究竟缺了什麽呢?下面,我會逐步闡述我的優化思路。首先,普遍的優化思路是基礎,我們先來看看在普遍的優化思路下,基本的前端高並發策略是怎麽樣的?

二、分析本質痛點

高並發場景,與普通場景的核心區別是並行的訪問量激增。因此,前端高並發策略本質要解決的是由訪問量激增帶來的問題。那訪問量激增帶來的是什麽問題呢?

我們先來看一張H5正常的訪問流圖:

技術分享

正常情況下,從用戶端到後臺的數據流動是很均衡的,用戶的訪問量在後臺可承受的範圍內。

而在高並發場景下,若不進行任何的高並發策略應對,原訪問流圖會變成這樣(前端到後臺紅色部分的請求會被後臺拒掉甚至可能會擊垮後臺):

技術分享

圖中可以很明顯地看出高並發的痛點:數據流動過程兩端失衡了。解決這一痛點,需要把兩端重新回到數據流動的平衡狀態。可以從兩方面著手,一方面是後臺層面盡可能地提供更大的承載能力(如增加機器等);另一方面則是在前端層面盡可能地加強其作為用戶與後臺之間的“門”的精簡過濾能力。

加強前端“門”的精簡過濾能力後,我們期望看到的訪問流圖是這樣的:

技術分享

雖然用戶並發量很大,但在前端高並發策略下,兩端失衡這一痛點得到了解決。那這些高並發策略都有哪些呢?我們來一個個地尋找。

三、尋找可行技術方案

前端“門”的角色要加強的是兩方面的能力,一個是精簡,另一個是過濾。

精簡

首先,我們先看精簡的技術方案。如果把後臺的承載能力比作成一個“圓”,那前端和後臺之間的通道就相當於一個以此圓為出口寬度的水管,其中的水可以理解為H5中的請求。而這樣的圓在H5中,實際有兩個,一個是最大並發數、一個是最大流量。對應的則是我們並行請求中的請求數和請求大小,精簡這兩者,即可在“圓”的面積固定的情況下,提供更多的“水”進出。

所以,在精簡的技術方案上,需要能針對並行請求中的請求數和請求大小進行精簡。

1、請求數精簡

當請求數從邏輯層面已無法再精簡時(如去掉一些無用請求),這時我們往往會將焦點聚焦到純技術方案上。

H5請求數精簡的方案,目前大致方案如下,核心為:合並。

技術分享

圖中列出的是H5中常用的資源類型(還有別的如視頻、音頻,不一一列舉)。可以看出,就圖中列出的目前的技術,對於請求數的減少,可以說要多極致可有多極致。極端情況下,一個業務只有一個請求也是可以做到的。

2、請求大小精簡

同樣的,當請求大小從邏輯層面已無法再精簡時(如去掉一些無用函數、代碼),這時我們往往會將焦點聚焦到純技術方案上。H5請求大小精簡的方案,目前大致方案如下,核心為:壓縮

技術分享

可以看到,就目前的技術,對於請求大小的精簡,每種資源都可以進一步的壓縮精簡。

過濾

上面說的是前端“門”精簡能力的技術方案,那下面我們再來看下前端“門”過濾能力的技術方案。還是剛剛水管的比喻,前端的過濾,可以理解為在前端“門”上加了一層可反彈特定水的網,用於把無須進入的水反彈掉(不知道這個比喻於水而言是否恰當,總之要表達的就是類似這麽個道理)。能把“水”反彈的方式有很多種,一種是被動式的,即只允許特定量的水通過,超了的部分就進不來了,這策略一般用於後臺,叫“過載保護”;另一種則是主動式的,通過對數據時效性的犧牲把數據往更前的一端進行儲存。在前端層面,一般叫“本地緩存”,當請求時發現前端有緩存的內容,就不用再去訪問服務器了。

所以,在過濾的技術方案上,前端可以通過緩存來完成。

1、緩存過濾請求

H5請求過濾的方案,目前大致方案如下,核心為:緩存。

技術分享

通過具體的前端緩存技術,可將原本需要通過到達後臺的請求直接從前端緩存處獲取而達到“過濾”的效果。

四、普遍優化思路下的基本策略

完成上述兩步——分析本質痛點、尋找可行技術方案,接下來大家普遍的做法是選擇其中的合適方案,然後用到我們的項目中。對於合並,我們會把同類型的文件統一做下合並;對於壓縮,我們會把沒壓縮的代碼都統一做下壓縮;對於緩存,我們統一啟用較長的http cache、使用localstorage緩存、使用離線包。整體策略下來,雖然在一定程度上會有效果,但是我認為這往往又是不夠的。要做到更徹底的優化,就需要對優化方案和優化場景本身做更深入的思考和策略調整。而這,往往是需要靠相應的思維模式驅動的。下面我來分別說說我總結出來的一些適用於更深層優化的思維,其中會著重談談差異化思維。

差異化思維

差異化思維,講求的是在深入理解技術與場景後,對技術與場景進行差異化分解,以達到每個差異場景的進一步技術最優。

從前兩步中——分析本質痛點、尋找可行技術方案,我們了解到高並發應對在前端技術層面可以從合並、壓縮、緩存三方面著手。一個很淺顯的道理是,這些策略做得越徹底,前端層面能擋掉的並發量就越多。但事實上,往往我們卻又並不能這麽做,而只能選取其中一種比較折中的方案。

比如,考慮到對頁面訪問耗時的影響,我們並不會把整個H5項目資源合並成為一個請求。原因在於,從本質上來說,每項純技術策略,有其優點的同時,就必然會帶來或多或少的缺點,正所謂萬物有利必有弊。而當這個弊端成為了影響項目核心能力(如體驗方面能力中的頁面訪問耗時)的時候,即使是能更好地提高並發能力的方案,在利弊權衡之後往往最終也並不會采用。這就是我前面說的只選擇折中的優化方案時優化不夠徹底的原因。

在利弊權衡之下,往往我們會選擇一個折中方案(如下圖那樣中選擇的策略3):

技術分享

而更徹底的優化應該是,了解每個方案所導致的弊端影響,由弊端影響對項目場景進行差異化分析,按各場景對弊端影響面的容忍程度,實行策略方案的差異化。對於能接受弊端影響的場景,使用最優方案;而對於不太能接受弊端影響的場景,使用較優方案;依次類推到使用折中方案。從而做到差異化的精細優化。優化後的總體策略方案會變成類似下圖的形式,原有項目只是單純的使用折中策略3,而差異化處理後,會抽離出部分項目模塊使用更優的策略1和策略2。

技術分享

下面,我會使用這種思維,對上面兩步得出的前端高並發中的三種策略——合並、壓縮、緩存進行進一步的差異化優化。

差異化的合並策略

代碼合並,合並到一定程度其弊端就會逐步放大顯露。

弊端有:單個請求過大,造成對頁面首屏渲染耗時的影響;動靜請求合並後(cgi+html),緩存時效性的要求會大大地提高(緩存時效性取決於各合並資源中要求最高者,木桶原理)。

根據每個弊端的影響,下面針對具體場景進行差異化分析。

1、“資源首屏體驗相關度”差異化分解

對於合並後單文件過大,其影響的是頁面首屏渲染耗時。那我們可以從影響點出發,對頁面網絡資源請求按首屏體驗相關性(影響點)進行差異化拆分,從而最大程度地減少合並對體驗的影響。最簡單的我們可以把資源分成兩部分——高相關性資源(首屏)和低相關性資源(非首屏)。每部分資源單獨策略處理,盡可能地做到極致的合並,提高並發能力。當再次遇到合並後文件太大而影響渲染耗時時,則在本級中再進一步分級,以此類推。如對於css、首屏渲染相關的js和圖片資源,可作為高相關性資源,把圖片base64進css,然後再全部內聯進html頁面,與頁面合並。對於非首屏相關的js和圖片資源,作為低相關性資源單獨合並。這樣即可在不影響頁面首屏渲染耗時體驗的同時,又保證了最大程度的減少並發請求數。

2、"資源時效依賴度"差異化分解

對於動靜請求合並(cgi+html),其影響的是緩存時效性,導致緩存時效性要求變高。那我們也可以從影響點出發,對頁面請求按時效依賴度(影響點)進行差異化拆分。最簡單的我們可以把頁面分成兩類——高時效性要求頁面(入口不可控,本來就不做緩存)和低時效性要求頁面(入口完全可控,可通過修改頁面離線包等方式做更新,可緩存)。對於高時效性要求的頁面,動靜請求合並後不會對該類頁面有影響,此類頁面可將cgi和html進行合並。而對於低時效性要求的頁面,這類頁面是可以緩存的(如使用離線包),則不進行cgi和html合並。

針對具體場景,差異化地采用相應最優的合並策略,優化效果將會再進一步地提升。

差異化的壓縮策略

同樣的,代碼壓縮,也有其弊端。弊端有:壓縮程度越高,代碼可讀性越差,不便於線上問題的定位;雖有更優的壓縮算法,但算法本身又存在自身的局限性。

1、“資源可讀依賴度”差異化分解

對於代碼可讀性的影響,市面上其實已有代碼層面的解決方案,如項目支持debug模式切換(此解決思路就是一種差異化思維,按使用場景差異化分成代碼可讀性要求高場景和代碼可讀性要求低場景,如線上的代碼屬於代碼可讀性要求低的,采用極端壓縮版的代碼;開發debug模式下的代碼屬於代碼可讀性要求高的,采用非壓縮版的代碼,兩種模式可參數化切換)、sourcemap等。

2、“資源平臺支持程度”差異化分解

對於各壓縮算法的局限性(或者說各壓縮算法下的產物的局限性,如圖片資源有多種格式,而每種格式又有其局限性),其影響的是其所不支持(局限外)的那部分平臺,會導致那部分平臺無法使用。那我們從影響點出發,可以對該網絡資源按平臺支持程度(影響點)進行差異化拆分。我們可以按壓縮算法的壓縮效果進行方案的排序,從高到低地對方案的平臺支持程度進行差異化判斷篩選,支持則使用當前算法類型(格式),不支持則判斷使用下一種算法類型(格式)。

例如對於圖片資源,圖片的格式豐富多樣,多樣的格式實際來源於每種格式使用的壓縮算法的不同,都有其擅長的領域。這時,我們不能只用一種最普遍適用的格式,而應該利用上面的差異化思路來加載圖片。按照各圖片格式的壓縮程度,對於支持tpg(公司內叫sharpp)的平臺請求tpg格式圖片,不支持tpg的則再去判斷是否支持webp;支持webp的平臺則請求webp格式的圖片,若不支持webp則再往下判斷。而針對圖片格式的擅長領域,對於色彩豐富的圖片采用jpg格式、色彩較簡單或需要透明通道的采用png格式,按最適合的進行圖片格式差異化選取。甚至對於圖片的尺寸(尺寸與壓縮無關,但目的都是為了減少請求大小,故用於類比),我們也可以采用這個差異化思路,如根據當前客戶端的分辨率,返回最適大小的圖片給客戶端,從而做到高分辨率客戶端請求返回高分辨率圖片,低分辨率客戶端請求返回低分辨率圖片。

針對具體場景,差異化地采用相應最優的壓縮策略,優化效果也將會再進一步地提升。

差異化的緩存策略

與上面的類似,緩存策略同樣也有相應的弊端。弊端有:緩存時間越長,數據的準確性就越差,會存在緩存數據雖有效但已與最新數據有較大差異的問題。

1、“資源時效依賴度”差異化分解

針對資源有效性受緩存時間長短的影響,我們可以對資源進行時效性分級。可大致分成更新可控資源和更新不可控資源。此處的可不可控指的是資源更新後頁面能否實時感知到更新。對於前端開發人員部署的js、css、圖片等資源,均可作為更新可控資源,設置極長緩存,因為這類資源更新的同時可以將版本信息實時同步給前端頁面(如拉取的文件改名了、改時間戳了等)。而對於無法實時同步版本信息給前端頁面的資源,可作為更新不可控資源。對於這部分資源,我們可以再根據業務對各資源的時效性要求程度進行差異化分級。

以手Q中的H5項目中用到的QQ頭像資源為例,此場景下頭像是一個對項目更新不可控的資源。用戶通過使用手Q或PC QQ修改自身頭像後,各H5項目對於這個修改是無感知的,H5並不會實時地收到更新通知(除非雙方在接口層面上做同步通知)。此時,如果頭像緩存時間設置較長,就會出現用戶更新了頭像,但在H5項目中看到的頭像還是舊的的情況。但如果不緩存,在高並發場景下勢必對頭像服務器造成極大的並發壓力。這時,就需要對這一更新不可控資源做進一步差異化分解。

對於手Q中社交性較強的H5項目(如手Q紅包、手Q AA收款等),其中雖然有很多頭像,但是各頭像對時效性的要求還是有差異的。最簡單的我們可以把頭像分成兩類,高時效性頭像和低時效性頭像。稍作分析後,其實可以發現,對於使用者而言,用戶自身(主人態)的頭像變更是最敏感的,如果用戶在手Q或PC QQ上修改完自己的頭像後,進入該H5後發現自己的頭像沒有變是不太能容忍的。此時,用戶自身的頭像可作為高時效性頭像。而對於其他用戶(客人態)的頭像的時效性,變沒變,使用者其實倒不會太過在意,所以對於非用戶自身的頭像可作為低時效性頭像。最後在策略上,對於高時效性頭像,緩存一個較短時間;對於低時效性頭像,則緩存一個相對較長時間。(實現起來也很容易,可將差異化邏輯放在前端判斷然後加時間戳決定緩存時間即可。)

針對具體場景,差異化地采用相應最優的緩存策略,優化效果也將會再進一步地提升。

五、更多“維”的優化

在差異化思維的指導下,高並發優化策略得到了更進一步的完善。該思維的核心思想是針對方案的優缺與實際場景進行差異權衡。從通用性角度來看,這項思維也適用於工作上的很多事情,是一項通用化的思維,而並不僅僅局限於使用在解決前端高並發這個問題點上。同時,也並非所有的方案都只采用差異化思維就能完美地解決問題。差異化思維只是眾多思維中的一種,實際上,還有很多思維。一個優秀的優化方案往往是在多“維”的思考權衡下的最終產物。

邊界放大思維

比如,邊界放大思維,指的是我們在做一項事情的時候,視野不應只停留在自己所能完全把控的領域,而應該把邊界放大,從更外圍的視野來思考這個問題的解決方案。

如前面說到的緩存策略,其實有一個當前H5的緩存策略弊端需要通過使用邊界放大思維來優化。這個弊端是:當前瀏覽器緩存技術有其自身的局限性,緩存的有效性依賴於用戶的二次訪問程度。弊端的核心問題在於:緩存時機與用戶首次訪問相耦合。這使得在一些超高並發的H5活動中(活動類與業務類H5不同,活動類H5大部分是第一次訪問的用戶),緩存帶來的效果並沒有想象中大。

這是在純前端技術層面無法解決的。但當我們把思維邊界放大,擴展考慮到承載H5的平臺時,這個弊端也許就能獲得解決。因為這個弊端核心要解決的是資源的緩存與頁面訪問解耦的問題,而承載平臺方(尤其終端)是有這個能力完成的。如在手Q中,這個解決方案叫“離線包”。離線包支持被動緩存的同時,也支持主動緩存。可將頁面內容無須用戶主動訪問而通過預下載或主動push的方式緩存到用戶手Q客戶端上。首次訪問的用戶也可直接命中緩存。這樣即可大大提高緩存的有效性。春節期間,手Q各高並發H5無不使用這項技術來提高頁面的高並發能力。

邏輯全面性思維

再如,邏輯全面性思維,指的是我們在做一項事情的時候,視野不能只停留於邏輯的局部,而應該看到邏輯的全狀。如邏輯有正常狀態,也有異常狀態,我們不能只考慮正常狀態。邏輯有雙向也有單向,對於雙向的邏輯,我們不能只考慮其中的正向。

其實,前面我說的所有前端高並發策略(包括前面畫的圖),都僅僅只考慮到了數據流的正向邏輯段,即數據從用戶端流向服務端的過程。而數據流的反向邏輯段其實是沒有考慮的(即數據從服務端回到用戶端的這一段邏輯情況)。而在高並發場景下,數據的反向邏輯段往往也會作為邏輯中非常關鍵的一環。沒考慮數據流反向邏輯段的高並發策略,優化數據再好也只能說完成了一半。下面是數據流動的全邏輯過程(紅色部分是數據流動的反向邏輯段):

技術分享

在數據流的反向邏輯段中,前端在這層邏輯中的角色變成了數據接受方,而接受的數據可能存在多種狀態,前端需要對這些狀態都做好相應的處理。在高並發下,若後臺過載了,那就會有部分數據返回異常。此時,最簡單的我們可以分成兩種狀態——成功、失敗,成功狀態頁面正常展示,異常狀態則需要盡可能做到體驗降級而非完全不可用。如靜態資源cdn請求失敗了,前端可以進行這樣的一層異常邏輯處理:將當前異常用戶的靜態資源域名臨時切換到備份域名(如頁面域名或備用域名),這樣可以將本來應該白屏無法使用的體驗降級到訪問速度較慢的體驗,同時也給了錯誤率超過閥值時的cdn機器擴容提供調整時間。當然,若再結合上面的差異化思想,我們還可以將當前服務器的總體狀態進行差異化分級(如當前負載程度),通過配置返回等策略告知頁面當前服務器並發情況的程度,前端針對這些狀態做差異化處理,逐步降級。如當cgi並發超過一定限度時,前端可以考慮將一些非核心但訪問量較高的cgi的頁面入口逐步屏蔽掉,到最後僅保留核心的cgi入口,從而保障項目核心功能不受高並發影響。

五、結語

本文算是筆者從事4年前端工作以來的一些思考。基於前端高並發策略這一優化點,向各位逐步闡述筆者在其“術”方面和“思維”方面的一些思路。受限於自身的經歷和視野,觀點也許有其局限性。希望文中提到的策略和思維,能給能作為讀者的你一些收獲。文章篇幅較長,文字較多,感謝耐心閱讀!


關於騰訊WeTest (wetest.qq.com)

騰訊WeTest是騰訊遊戲官方推出的一站式遊戲測試平臺,用十年騰訊遊戲測試經驗幫助廣大開發者對遊戲開發全生命周期進行質量保障。騰訊WeTest提供:適配兼容測試;雲端真機調試;安全測試;耗電量測試;服務器性能測試;輿情監控等服務。

點擊地址:http://wetest.qq.com/立即體驗!

多 “維” 優化——前端高並發策略的更深層思考