Web前端很多優(yōu)化原則都是從如何提升網(wǎng)絡(luò)通訊效率的角度提出的,但是這些原則使用的時候還是有很多陷阱在里面,如果我們不能深入理解這些優(yōu)化原則背后所隱藏的技術(shù)原理,很有可能掉進這些陷阱里,最終沒有達到最佳的預(yù)期效果,今天我在這里分析下瀏覽器和服務(wù)端通訊的一些細節(jié)問題,希望通過分析這些細節(jié)問題,能給大家一個啟迪,能更好的理解這些優(yōu)化原則背后的隱秘,最終能更好的運用這些原則。
網(wǎng)站的通訊技術(shù)是構(gòu)建在http協(xié)議上,http協(xié)議底層通訊手段使用的是tcp/ip協(xié)議,但是tcp通訊協(xié)議在建立連接和斷開連接這兩個動作上是非常消耗通訊性能的,這主要是因為tcp/ip協(xié)議在連接建立時候的三次握手機制和斷開連接時候的四次揮手機制所致,我們來看看下面的圖形:
圖中中間被紅色標記的方塊就是tcp/ip協(xié)議在建立連接時候需要發(fā)送三次報文才能確認連接是否建立成功,中間四個藍色的方框就是說明tcp/ip協(xié)議在斷開連接時候要發(fā)四次報文才能確定連接最終被斷開,而一個具體的http請求和響應(yīng)也就發(fā)送兩次報文,這也就說明如果瀏覽器每次和服務(wù)端的交互都要新建和關(guān)閉一個tcp/ip連接,那么瀏覽器和服務(wù)器之間就要往返9次報文通訊,而真正用來處理用戶請求的報文確只有其中的兩次,換句話說這樣的一個請求大概會有80%左右的性能都不是用來處理業(yè)務(wù)需求,等于是損失了80%左右的性能,當然這個比率是9次報文交互的數(shù)據(jù)大小一致情況下得出的,如果用戶業(yè)務(wù)請求和響應(yīng)的數(shù)據(jù)量比較大,那么建立連接和斷開連接的性能損失占比會降低,不過就算占比降低了那也是在請求處理本身的時間變的更慢的基礎(chǔ)上的降低,要是瀏覽器和服務(wù)器之間的距離特別大,那么多出來的7次報文交換的效率問題就更加嚴重了,不管怎樣,tcp/ip的三次握手機制和四次揮手機制只要發(fā)生都會對網(wǎng)絡(luò)請求效率產(chǎn)生重大影響。
為了解決這個報文交互次數(shù)過多的問題,http協(xié)議本身也發(fā)生了改變,那就是http開始采用了長連接,使用長連接后網(wǎng)站只需要開啟一個長連接,在用戶關(guān)閉瀏覽器關(guān)閉之前瀏覽器里的網(wǎng)頁都會復(fù)用這個長連接。不過http協(xié)議的1.0版本默認是不啟用長連接的,所以在使用http協(xié)議1.0版本時候我就得手動的打開長連接,這個方法就是在http頭里設(shè)置Connection: Keep-Alive,而http1.1版本里長連接是默認打開的,所以不需要我們手動的設(shè)置,而且時下的瀏覽器幾乎都支持http1.1協(xié)議,因此大多時候情況下我們是沒有必要手動去打開長連接的。
雖然http協(xié)議采用長連接后可以減少網(wǎng)站通訊時候三次握手和四次揮手的次數(shù),但是長連接建立起來后需要瀏覽器和服務(wù)器長時間維護,這本身會消耗瀏覽器和服務(wù)器的性能,特別是服務(wù)器端長時間維護長連接本身還會損壞服務(wù)器處理并發(fā)的能力,所以早期瀏覽器會限制http1.1開啟連接的數(shù)量,例如ie7這個古董瀏覽器,它準許http1.1最多開啟2個長連接,而http1.0因為默認使用短連接它默認可以開啟4個,下面有張圖可以說明,如下所示:
提升瀏覽器加載效率的手段除了提升每個連接的傳輸效率外,其實還有一種方式,這個方式就是使用多個連接進行并行加載,這個等于幾個人聯(lián)合起來一起完成一個任務(wù),那么效率肯定就比一個人高,而頁面加載時候很符合使用并發(fā)加載的場景,例如我們讓頁面里的圖片并行加載肯定會比一個個加載圖片的效率要高多了?;氐綖g覽器支持的連接數(shù)的問題,由于早期瀏覽器在http1.0和http1.1連接數(shù)的差異,某些網(wǎng)站例如維基百科這樣的網(wǎng)站,它的靜態(tài)資源特別多,為了充分發(fā)揮并發(fā)的優(yōu)勢,它將存放這些靜態(tài)資源的服務(wù)器采用http1.0協(xié)議,這樣就能并行加載更多的靜態(tài)資源,因為這個并行加載的總體效率提升相比tcp/ip握手和揮手的損失要高的多,不過現(xiàn)在這個手法已經(jīng)起不到什么作用了,因為新版的瀏覽器已經(jīng)把兩種版本的http協(xié)議支持的連接數(shù)調(diào)整一致了,因為長連接可以復(fù)用鏈路,因此使用長連接的效率會比非長連接更好。
上面連接數(shù)也是有一個限制的,這個限制就是必須是在同一個域名下,如果一個頁面某些靜態(tài)資源放在不同域名下面,那么這個做法就可以增加頁面里的并發(fā)數(shù)量,例如我們把一些不是經(jīng)常變化的靜態(tài)資源例如圖片、外部的css文件以及javascript文件單獨放置在一個靜態(tài)資源服務(wù)器上,靜態(tài)資源服務(wù)器對外的url地址和頁面本身的url地址不在同一個域名下,那么頁面本身的并發(fā)加載連接數(shù)就會增加一倍,不過這也就意味著瀏覽器端要維護的長連接數(shù)會變得更多,雅虎工程師曾經(jīng)總結(jié)過一個頁面里合理的域名數(shù)量,那就是兩個,這個結(jié)論的提出已經(jīng)過去了好多年了,現(xiàn)在的瀏覽器和服務(wù)器的性能已經(jīng)今非昔比了,這個跨域數(shù)量應(yīng)該可以增加點,不過我個人認為一個頁面的里包含的域名數(shù)量還是不要太多,其實如果我們web前端優(yōu)化手段使用得當,兩個不同域名就足夠用了,多了價值不大,除非你網(wǎng)站情況是在特殊,例如你看看現(xiàn)在瀏覽器本身支持的連接數(shù)量已經(jīng)很高了,大部分都是6,ie9甚至還達到了10,翻個倍就有12和20個連接數(shù),我們在翻個倍就是24和40個,這個數(shù)字看起來就很恐怖了,一個計算機支持這么多并發(fā),假如你在瀏覽器還打開個網(wǎng)站也是這么干的,那么瀏覽器的并發(fā)數(shù)多的實在太嚇人了,我估計到時計算機本身就跑不動了,所以10多個連接數(shù)很夠用了,你合理發(fā)揮下這些連接數(shù)網(wǎng)站的性能就能有很大提升,再說了一個網(wǎng)站并發(fā)連接數(shù)太多那本身就說明了你在減少http個數(shù)這個手段沒有運用好。
回到web前端優(yōu)化的手段,我們?nèi)绻堰@些手段再仔細分析下就會發(fā)現(xiàn)很多手段使用都是在同步請求這個場景下進行了,當然這些手段在合適情況下也能作用于異步加載場景,但是異步加載場景發(fā)生并發(fā)加載之前需要一個單線程的異步加載,這個單線程的異步加載就和分布式系統(tǒng)里的單點故障有點像了,它很有可能是整個流程的軟肋所在,所以合理使用同步請求還能讓異步操作性能更加優(yōu)秀做好準備。上面我講到瀏覽器在同一個域名下最多可以開啟多少個連接數(shù),但是從事web前端開發(fā)的人都能感覺到,我們做頁面開發(fā)時候其實是沒法控制這個連接數(shù)的,那么問題來了,這么多連接到底是在什么條件下被開啟的呢?這個問題非常有意思的,我們來看下面的瀑布圖:
從上面的瀑布圖我們發(fā)現(xiàn),并行下載的是圖片,這個推而廣之要是我們看見某些網(wǎng)站的網(wǎng)頁做過并發(fā)優(yōu)化處理的設(shè)計,我們就會發(fā)現(xiàn)并發(fā)的資源都是純靜態(tài)的資源,那么這個并發(fā)連接數(shù)跟我們頁面的設(shè)計存在一個怎樣的關(guān)系呢?首先我們總結(jié)一下頁面里的靜態(tài)資源,在頁面里靜態(tài)資源有html,如果html里面有內(nèi)聯(lián)的css代碼和javascript代碼,那么這些代碼也會歸屬于html,除了html外還有外部的css文件、外部的javascript文件和頁面里使用到的圖片,那么這些要素怎樣會促發(fā)頁面的并行加載了,換個說法這些要素又是如何促使瀏覽器同時打開更多連接呢?
首先我們要明確一個問題,瀏覽器之所以可以打開更多連接數(shù),讓這么多連接并行執(zhí)行是有個前提的,這個前提就是這些資源是不是被并行加載的,例如像外部css文件,圖片這樣的資源,這些資源下載完畢后馬上就可以使用,因為它們下載完畢后沒有邏輯性問題要處理因此下載完畢后就可以直接拿來使用,因此它們并行加載不會影響到頁面的展示問題,這個情況如果碰到j(luò)avascript就有點麻煩了,外部javascript代碼是包含邏輯在里面,而且有些邏輯很有可能會影響頁面的展示,所以javascript下載完畢后,瀏覽器就得馬上執(zhí)行,所以我們就會看到這樣的瀑布圖,如下圖所示:
上面的空白區(qū)就是瀏覽器在執(zhí)行javascript代碼所要花費的時間。瀏覽器開啟多少個連接是瀏覽器自發(fā)的行為,這個自發(fā)行為主要出于提升瀏覽器并發(fā)下載效率的角度出發(fā)的。由于現(xiàn)在瀏覽器的連接基本都是采取的是http1.1協(xié)議,也就是使用的長連接,那么連接建立后這個連接就會長期維護,如果這個長連接是單獨的靜態(tài)資源服務(wù)器上的長連接,這個問題倒沒什么,如果這個長連接放在主域名下面,問題就來了,主域名在頁面初始化加載時候會用來下載html,如果我們?yōu)樘岣卟l(fā)下載效率,讓這個主域名下還放置其他的靜態(tài)資源,那么可能會導(dǎo)致瀏覽器和主域名的服務(wù)器下維護更多的長連接,而頁面后續(xù)操作基本是使用ajax來操作的,而ajax往往只會復(fù)用其中一個長連接,那么其他多余的長連接等于要空轉(zhuǎn)了,這個空轉(zhuǎn)還需要消耗瀏覽器和服務(wù)器的系統(tǒng)資源,所以我們發(fā)現(xiàn)主域名下的請求資源類型一定要認真加以控制,能遷移到單獨的靜態(tài)資源服務(wù)器上的一定要進行遷移,盡量讓主域名下處理的請求都是包含業(yè)務(wù)邏輯的請求,這樣就可以有效提升系統(tǒng)資源的使用率。這個問題進一步思考下去,我們就會發(fā)現(xiàn)如果服務(wù)端的業(yè)務(wù)應(yīng)用服務(wù)器之前放置一個反向代理,反向代理都是使用靜態(tài)資源服務(wù)器,而靜態(tài)資源服務(wù)器對并發(fā)的承載能力是遠超業(yè)務(wù)應(yīng)用服務(wù)器,如果主域名下我們不小心放置了太多靜態(tài)資源,要是后臺使用了反向代理,那么反向代理也可以減輕這種長連接所造成的計算資源損失。
上面這些場景都是在瀏覽器同步請求下進行了,那么換到異步請求這個并行加載靜態(tài)資源的手段還有效嗎?回答這個問題前,我們首先要想想異步加載會導(dǎo)致新的靜態(tài)資源被加載嗎?這個當然可能,特別是在前端MVC的場景下,我們會把模板技術(shù)放到瀏覽器端完成,這個時候有些html模板一開始可能會包含在javascript代碼里,作為一個變量存儲下來,而這個模板里很有可能包含好多新的圖片被使用,當ajax從服務(wù)端獲取數(shù)據(jù)后,解析了這個模板,然后我們把構(gòu)造好的模板加入到頁面的DOM結(jié)構(gòu)里,瀏覽器重新渲染頁面時候看到很多新圖片需要加載,就有可能會開啟多個連接進行并行加載來提升資源加載效率,如果碰到通過ajax技術(shù)動態(tài)加載外部CSS文件,那么這個并行加載情況就會更加突出了,因為css文件里很有可能包含大量的圖片資源,如果我們把不變的靜態(tài)資源都放置在了單獨的靜態(tài)資源服務(wù)器,那么這個并行加載就不會在主域名下打開更多長連接,由此可見,將靜態(tài)資源使用單獨的域名的靜態(tài)資源服務(wù)器處理的好處非常之多。
現(xiàn)在http2.0協(xié)議還在起草之中,http2.0如果落地將會給web前端優(yōu)化技術(shù)產(chǎn)生重大影響,http2.0打算在一個頁面里只使用一個tcp/Ip連接,不過http2.0會在這個連接上進行鏈路復(fù)用,也就是讓一個連接上也能做到并行操作,讓連接的利用率更高,如果http2.0落地后,web前端里那些用于減少http連接數(shù)的手段都會失去市場了,因為協(xié)議本身就能處理好并發(fā)的問題了,到時像外部css文件,外部javascript文件,css sprite技術(shù)說不定就要成為歷史了。
看來本主題又寫不完了,下篇接著寫吧,今天是元宵節(jié),這里我祝大家節(jié)日快樂。
聯(lián)系客服