一項指標的變好,總少不了相應優化策略的實施。優化並非簡單的一蹴而就,而是個不斷迭代與推翻的過程。更深層的優化方案,每每是在某種思惟策略之下,對問題場景和基本策略優缺的深入理解後作出的當下最優的權衡結果。本文筆者從前端高併發優化這一具體點出發,逐步向你們闡述筆者在優化的「術」之上思惟層面的一些思考。但願能給各位帶來共鳴和感悟。css
背景:
之因此會之前端高併發這一主題入手,一來是本人曾負責過一些超高併發量的業務(手Q紅包),在這方面算是有些經驗。二來是相對於業務功能優化這類光前端層面的邏輯就涉及產品、設計等多方人員合做討論而完成的優化(即邏輯自己並不是純出自前端人員的腦子),前端高併發這種前端層面邏輯純由前端人員全把控的優化,或許做爲前端的我,能得出來的思考觀點會更深入和更通用一些。html
說到優化,你們在收到「優化指標」任務的時候。一般會作兩件事情——分析「優化指標」對應的痛點、尋找解決痛點的技術方案並施行。那這樣是否就足夠了呢?個人答案是否認的。在個人認知裏這只是第一層的優化,雖然在結果上每每咱們使用更優的技術後確實能夠達到更好的優化效果,但卻又不那麼完美,優化效果還能夠作得更好。那究竟缺了什麼呢?下面,我會逐步闡述個人優化思路。首先,廣泛的優化思路是基礎,咱們先來看看在廣泛的優化思路下,基本的前端高併發策略是怎麼樣的?前端
高併發場景,與普通場景的核心區別是並行的訪問量激增。所以,前端高併發策略本質要解決的是由訪問量激增帶來的問題。那訪問量激增帶來的是什麼問題呢?web
咱們先來看一張H5正常的訪問流圖:算法
正常狀況下,從用戶端到後臺的數據流動是很均衡的,用戶的訪問量在後臺可承受的範圍內。瀏覽器
而在高併發場景下,若不進行任何的高併發策略應對,原訪問流圖會變成這樣(前端到後臺紅色部分的請求會被後臺拒掉甚至可能會擊垮後臺):緩存
圖中能夠很明顯地看出高併發的痛點:數據流動過程兩端失衡了。解決這一痛點,須要把兩端從新回到數據流動的平衡狀態。能夠從兩方面着手,一方面是後臺層面儘量地提供更大的承載能力(如增長機器等);另外一方面則是在前端層面儘量地增強其做爲用戶與後臺之間的「門」的精簡過濾能力。服務器
增強前端「門」的精簡過濾能力後,咱們指望看到的訪問流圖是這樣的:網絡
雖然用戶併發量很大,但在前端高併發策略下,兩端失衡這一痛點獲得瞭解決。那這些高併發策略都有哪些呢?咱們來一個個地尋找。併發
前端「門」的角色要增強的是兩方面的能力,一個是精簡,另外一個是過濾。
首先,咱們先看精簡的技術方案。若是把後臺的承載能力比做成一個「圓」,那前端和後臺之間的通道就至關於一個以此圓爲出口寬度的水管,其中的水能夠理解爲H5中的請求。而這樣的圓在H5中,實際有兩個,一個是最大併發數、一個是最大流量。對應的則是咱們並行請求中的請求數和請求大小,精簡這二者,便可在「圓」的面積固定的狀況下,提供更多的「水」進出。
因此,在精簡的技術方案上,須要能針對並行請求中的請求數和請求大小進行精簡。
一、請求數精簡
當請求數從邏輯層面已沒法再精簡時(如去掉一些無用請求),這時咱們每每會將焦點聚焦到純技術方案上。
H5請求數精簡的方案,目前大體方案以下,核心爲:合併。
圖中列出的是H5中經常使用的資源類型(還有別的如視頻、音頻,不一一列舉)。能夠看出,就圖中列出的目前的技術,對於請求數的減小,能夠說要多極致可有多極致。極端狀況下,一個業務只有一個請求也是能夠作到的。
二、請求大小精簡
一樣的,當請求大小從邏輯層面已沒法再精簡時(如去掉一些無用函數、代碼),這時咱們每每會將焦點聚焦到純技術方案上。H5請求大小精簡的方案,目前大體方案以下,核心爲:壓縮
能夠看到,就目前的技術,對於請求大小的精簡,每種資源均可以進一步的壓縮精簡。
上面說的是前端「門」精簡能力的技術方案,那下面咱們再來看下前端「門」過濾能力的技術方案。仍是剛剛水管的比喻,前端的過濾,能夠理解爲在前端「門」上加了一層可反彈特定水的網,用於把無須進入的水反彈掉(不知道這個比喻於水而言是否恰當,總之要表達的就是相似這麼個道理)。能把「水」反彈的方式有不少種,一種是被動式的,即只容許特定量的水經過,超了的部分就進不來了,這策略通常用於後臺,叫「過載保護」;另外一種則是主動式的,經過對數據時效性的犧牲把數據往更前的一端進行儲存。在前端層面,通常叫「本地緩存」,當請求時發現前端有緩存的內容,就不用再去訪問服務器了。
因此,在過濾的技術方案上,前端能夠經過緩存來完成。
一、緩存過濾請求
H5請求過濾的方案,目前大體方案以下,核心爲:緩存。
經過具體的前端緩存技術,可將本來須要經過到達後臺的請求直接從前端緩存處獲取而達到「過濾」的效果。
完成上述兩步——分析本質痛點、尋找可行技術方案,接下來你們廣泛的作法是選擇其中的合適方案,而後用到咱們的項目中。對於合併,咱們會把同類型的文件統一作下合併;對於壓縮,咱們會把沒壓縮的代碼都統一作下壓縮;對於緩存,咱們統一啓用較長的http cache、使用localstorage緩存、使用離線包。總體策略下來,雖然在必定程度上會有效果,可是我認爲這每每又是不夠的。要作到更完全的優化,就須要對優化方案和優化場景自己作更深刻的思考和策略調整。而這,每每是須要靠相應的思惟模式驅動的。下面我來分別說說我總結出來的一些適用於更深層優化的思惟,其中會着重談談差別化思惟。
差別化思惟,講求的是在深刻理解技術與場景後,對技術與場景進行差別化分解,以達到每一個差別場景的進一步技術最優。
從前兩步中——分析本質痛點、尋找可行技術方案,咱們瞭解到高併發應對在前端技術層面能夠從合併、壓縮、緩存三方面着手。一個很淺顯的道理是,這些策略作得越完全,前端層面能擋掉的併發量就越多。但事實上,每每咱們卻又並不能這麼作,而只能選取其中一種比較折中的方案。
好比,考慮到對頁面訪問耗時的影響,咱們並不會把整個H5項目資源合併成爲一個請求。緣由在於,從本質上來講,每項純技術策略,有其優勢的同時,就必然會帶來或多或少的缺點,正所謂萬物有利必有弊。而當這個弊端成爲了影響項目核心能力(如體驗方面能力中的頁面訪問耗時)的時候,即便是能更好地提升併發能力的方案,在利弊權衡以後每每最終也並不會採用。這就是我前面說的只選擇折中的優化方案時優化不夠完全的緣由。
在利弊權衡之下,每每咱們會選擇一個折中方案(以下圖那樣中選擇的策略3):
而更完全的優化應該是,瞭解每一個方案所致使的弊端影響,由弊端影響對項目場景進行差別化分析,按各場景對弊端影響面的容忍程度,實行策略方案的差別化。對於能接受弊端影響的場景,使用最優方案;而對於不太能接受弊端影響的場景,使用較優方案;依次類推到使用折中方案。從而作到差別化的精細優化。優化後的整體策略方案會變成相似下圖的形式,原有項目只是單純的使用折中策略3,而差別化處理後,會抽離出部分項目模塊使用更優的策略1和策略2。
下面,我會使用這種思惟,對上面兩步得出的前端高併發中的三種策略——合併、壓縮、緩存進行進一步的差別化優化。
代碼合併,合併到必定程度其弊端就會逐步放大顯露。
弊端有:單個請求過大,形成對頁面首屏渲染耗時的影響;動靜請求合併後(cgi+html),緩存時效性的要求會大大地提升(緩存時效性取決於各合併資源中要求最高者,木桶原理)。
根據每一個弊端的影響,下面針對具體場景進行差別化分析。
一、「資源首屏體驗相關度」差別化分解
對於合併後單文件過大,其影響的是頁面首屏渲染耗時。那咱們能夠從影響點出發,對頁面網絡資源請求按首屏體驗相關性(影響點)進行差別化拆分,從而最大程度地減小合併對體驗的影響。最簡單的咱們能夠把資源分紅兩部分——高相關性資源(首屏)和低相關性資源(非首屏)。每部分資源單獨策略處理,儘量地作到極致的合併,提升併發能力。當再次遇到合併後文件太大而影響渲染耗時時,則在本級中再進一步分級,以此類推。如對於css、首屏渲染相關的js和圖片資源,可做爲高相關性資源,把圖片base64進css,而後再所有內聯進html頁面,與頁面合併。對於非首屏相關的js和圖片資源,做爲低相關性資源單獨合併。這樣便可在不影響頁面首屏渲染耗時體驗的同時,又保證了最大程度的減小併發請求數。
二、"資源時效依賴度"差別化分解
對於動靜請求合併(cgi+html),其影響的是緩存時效性,致使緩存時效性要求變高。那咱們也能夠從影響點出發,對頁面請求按時效依賴度(影響點)進行差別化拆分。最簡單的咱們能夠把頁面分紅兩類——高時效性要求頁面(入口不可控,原本就不作緩存)和低時效性要求頁面(入口徹底可控,可經過修改頁面離線包等方式作更新,可緩存)。對於高時效性要求的頁面,動靜請求合併後不會對該類頁面有影響,此類頁面可將cgi和html進行合併。而對於低時效性要求的頁面,這類頁面是能夠緩存的(如使用離線包),則不進行cgi和html合併。
針對具體場景,差別化地採用相應最優的合併策略,優化效果將會再進一步地提高。
一樣的,代碼壓縮,也有其弊端。弊端有:壓縮程度越高,代碼可讀性越差,不便於線上問題的定位;雖有更優的壓縮算法,但算法自己又存在自身的侷限性。
一、「資源可讀依賴度」差別化分解
對於代碼可讀性的影響,市面上其實已有代碼層面的解決方案,如項目支持debug模式切換(此解決思路就是一種差別化思惟,按使用場景差別化分紅代碼可讀性要求高場景和代碼可讀性要求低場景,如線上的代碼屬於代碼可讀性要求低的,採用極端壓縮版的代碼;開發debug模式下的代碼屬於代碼可讀性要求高的,採用非壓縮版的代碼,兩種模式可參數化切換)、sourcemap等。
二、「資源平臺支持程度」差別化分解
對於各壓縮算法的侷限性(或者說各壓縮算法下的產物的侷限性,如圖片資源有多種格式,而每種格式又有其侷限性),其影響的是其所不支持(侷限外)的那部分平臺,會致使那部分平臺沒法使用。那咱們從影響點出發,能夠對該網絡資源按平臺支持程度(影響點)進行差別化拆分。咱們能夠按壓縮算法的壓縮效果進行方案的排序,從高到低地對方案的平臺支持程度進行差別化判斷篩選,支持則使用當前算法類型(格式),不支持則判斷使用下一種算法類型(格式)。
例如對於圖片資源,圖片的格式豐富多樣,多樣的格式實際來源於每種格式使用的壓縮算法的不一樣,都有其擅長的領域。這時,咱們不能只用一種最廣泛適用的格式,而應該利用上面的差別化思路來加載圖片。按照各圖片格式的壓縮程度,對於支持tpg(公司內叫sharpp)的平臺請求tpg格式圖片,不支持tpg的則再去判斷是否支持webp;支持webp的平臺則請求webp格式的圖片,若不支持webp則再往下判斷。而針對圖片格式的擅長領域,對於色彩豐富的圖片採用jpg格式、色彩較簡單或須要透明通道的採用png格式,按最適合的進行圖片格式差別化選取。甚至對於圖片的尺寸(尺寸與壓縮無關,但目的都是爲了減小請求大小,故用於類比),咱們也能夠採用這個差別化思路,如根據當前客戶端的分辨率,返回最適大小的圖片給客戶端,從而作到高分辨率客戶端請求返回高分辨率圖片,低分辨率客戶端請求返回低分辨率圖片。
針對具體場景,差別化地採用相應最優的壓縮策略,優化效果也將會再進一步地提高。
與上面的相似,緩存策略一樣也有相應的弊端。弊端有:緩存時間越長,數據的準確性就越差,會存在緩存數據雖有效但已與最新數據有較大差別的問題。
一、「資源時效依賴度」差別化分解
針對資源有效性受緩存時間長短的影響,咱們能夠對資源進行時效性分級。可大體分紅更新可控資源和更新不可控資源。此處的可不可控指的是資源更新後頁面可否實時感知到更新。對於前端開發人員部署的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年前端工做以來的一些思考。基於前端高併發策略這一優化點,向各位逐步闡述筆者在其「術」方面和「思惟」方面的一些思路。受限於自身的經歷和視野,觀點也許有其侷限性。但願文中提到的策略和思惟,能給能做爲讀者的你一些收穫。文章篇幅較長,文字較多,感謝耐心閱讀!
做者:徐嘉偉,騰訊web前端開發 高級工程師
原文:http://www.cnblogs.com/wetest/p/7069005.html