關於大型網站技術演進的思考(二十)--網站靜態化處理—web前端優化—中(12)

夏天的森林發表於2015-03-07

  Web前端很多優化原則都是從如何提升網路通訊效率的角度提出的,但是這些原則使用的時候還是有很多陷阱在裡面,如果我們不能深入理解這些優化原則背後所隱藏的技術原理,很有可能掉進這些陷阱裡,最終沒有達到最佳的預期效果,今天我在這裡分析下瀏覽器和服務端通訊的一些細節問題,希望通過分析這些細節問題,能給大家一個啟迪,能更好的理解這些優化原則背後的隱祕,最終能更好的運用這些原則。

  網站的通訊技術是構建在http協議上,http協議底層通訊手段使用的是tcp/ip協議,但是tcp通訊協議在建立連線和斷開連線這兩個動作上是非常消耗通訊效能的,這主要是因為tcp/ip協議在連線建立時候的三次握手機制和斷開連線時候的四次揮手機制所致,我們來看看下面的圖形:

 

  圖中中間被紅色標記的方塊就是tcp/ip協議在建立連線時候需要傳送三次報文才能確認連線是否建立成功,中間四個藍色的方框就是說明tcp/ip協議在斷開連線時候要發四次報文才能確定連線最終被斷開,而一個具體的http請求和響應也就傳送兩次報文,這也就說明如果瀏覽器每次和服務端的互動都要新建和關閉一個tcp/ip連線,那麼瀏覽器和伺服器之間就要往返9次報文通訊,而真正用來處理使用者請求的報文確只有其中的兩次,換句話說這樣的一個請求大概會有80%左右的效能都不是用來處理業務需求,等於是損失了80%左右的效能,當然這個比率是9次報文互動的資料大小一致情況下得出的,如果使用者業務請求和響應的資料量比較大,那麼建立連線和斷開連線的效能損失佔比會降低,不過就算佔比降低了那也是在請求處理本身的時間變的更慢的基礎上的降低,要是瀏覽器和伺服器之間的距離特別大,那麼多出來的7次報文交換的效率問題就更加嚴重了,不管怎樣,tcp/ip的三次握手機制和四次揮手機制只要發生都會對網路請求效率產生重大影響。

  為了解決這個報文互動次數過多的問題,http協議本身也發生了改變,那就是http開始採用了長連線,使用長連線後網站只需要開啟一個長連線,在使用者關閉瀏覽器關閉之前瀏覽器裡的網頁都會複用這個長連線。不過http協議的1.0版本預設是不啟用長連線的,所以在使用http協議1.0版本時候我就得手動的開啟長連線,這個方法就是在http頭裡設定Connection: Keep-Alive,而http1.1版本里長連線是預設開啟的,所以不需要我們手動的設定,而且時下的瀏覽器幾乎都支援http1.1協議,因此大多時候情況下我們是沒有必要手動去開啟長連線的。

  雖然http協議採用長連線後可以減少網站通訊時候三次握手和四次揮手的次數,但是長連線建立起來後需要瀏覽器和伺服器長時間維護,這本身會消耗瀏覽器和伺服器的效能,特別是伺服器端長時間維護長連線本身還會損壞伺服器處理併發的能力,所以早期瀏覽器會限制http1.1開啟連線的數量,例如ie7這個古董瀏覽器,它准許http1.1最多開啟2個長連線,而http1.0因為預設使用短連線它預設可以開啟4個,下面有張圖可以說明,如下所示:

 

  提升瀏覽器載入效率的手段除了提升每個連線的傳輸效率外,其實還有一種方式,這個方式就是使用多個連線進行並行載入,這個等於幾個人聯合起來一起完成一個任務,那麼效率肯定就比一個人高,而頁面載入時候很符合使用併發載入的場景,例如我們讓頁面裡的圖片並行載入肯定會比一個個載入圖片的效率要高多了。回到瀏覽器支援的連線數的問題,由於早期瀏覽器在http1.0和http1.1連線數的差異,某些網站例如維基百科這樣的網站,它的靜態資源特別多,為了充分發揮併發的優勢,它將存放這些靜態資源的伺服器採用http1.0協議,這樣就能並行載入更多的靜態資源,因為這個並行載入的總體效率提升相比tcp/ip握手和揮手的損失要高的多,不過現在這個手法已經起不到什麼作用了,因為新版的瀏覽器已經把兩種版本的http協議支援的連線數調整一致了,因為長連線可以複用鏈路,因此使用長連線的效率會比非長連線更好。

  上面連線數也是有一個限制的,這個限制就是必須是在同一個域名下,如果一個頁面某些靜態資源放在不同域名下面,那麼這個做法就可以增加頁面裡的併發數量,例如我們把一些不是經常變化的靜態資源例如圖片、外部的css檔案以及javascript檔案單獨放置在一個靜態資源伺服器上,靜態資源伺服器對外的url地址和頁面本身的url地址不在同一個域名下,那麼頁面本身的併發載入連線數就會增加一倍,不過這也就意味著瀏覽器端要維護的長連線數會變得更多,雅虎工程師曾經總結過一個頁面裡合理的域名數量,那就是兩個,這個結論的提出已經過去了好多年了,現在的瀏覽器和伺服器的效能已經今非昔比了,這個跨域數量應該可以增加點,不過我個人認為一個頁面的裡包含的域名數量還是不要太多,其實如果我們web前端優化手段使用得當,兩個不同域名就足夠用了,多了價值不大,除非你網站情況是在特殊,例如你看看現在瀏覽器本身支援的連線數量已經很高了,大部分都是6,ie9甚至還達到了10,翻個倍就有12和20個連線數,我們在翻個倍就是24和40個,這個數字看起來就很恐怖了,一個計算機支援這麼多併發,假如你在瀏覽器還開啟個網站也是這麼幹的,那麼瀏覽器的併發數多的實在太嚇人了,我估計到時計算機本身就跑不動了,所以10多個連線數很夠用了,你合理發揮下這些連線數網站的效能就能有很大提升,再說了一個網站併發連線數太多那本身就說明了你在減少http個數這個手段沒有運用好。

  回到web前端優化的手段,我們如果把這些手段再仔細分析下就會發現很多手段使用都是在同步請求這個場景下進行了,當然這些手段在合適情況下也能作用於非同步載入場景,但是非同步載入場景發生併發載入之前需要一個單執行緒的非同步載入,這個單執行緒的非同步載入就和分散式系統裡的單點故障有點像了,它很有可能是整個流程的軟肋所在,所以合理使用同步請求還能讓非同步操作效能更加優秀做好準備。上面我講到瀏覽器在同一個域名下最多可以開啟多少個連線數,但是從事web前端開發的人都能感覺到,我們做頁面開發時候其實是沒法控制這個連線數的,那麼問題來了,這麼多連線到底是在什麼條件下被開啟的呢?這個問題非常有意思的,我們來看下面的瀑布圖:

 

  從上面的瀑布圖我們發現,並行下載的是圖片,這個推而廣之要是我們看見某些網站的網頁做過併發優化處理的設計,我們就會發現併發的資源都是純靜態的資源,那麼這個併發連線數跟我們頁面的設計存在一個怎樣的關係呢?首先我們總結一下頁面裡的靜態資源,在頁面裡靜態資源有html,如果html裡面有內聯的css程式碼和javascript程式碼,那麼這些程式碼也會歸屬於html,除了html外還有外部的css檔案、外部的javascript檔案和頁面裡使用到的圖片,那麼這些要素怎樣會促發頁面的並行載入了,換個說法這些要素又是如何促使瀏覽器同時開啟更多連線呢?

  首先我們要明確一個問題,瀏覽器之所以可以開啟更多連線數,讓這麼多連線並行執行是有個前提的,這個前提就是這些資源是不是被並行載入的,例如像外部css檔案,圖片這樣的資源,這些資源下載完畢後馬上就可以使用,因為它們下載完畢後沒有邏輯性問題要處理因此下載完畢後就可以直接拿來使用,因此它們並行載入不會影響到頁面的展示問題,這個情況如果碰到javascript就有點麻煩了,外部javascript程式碼是包含邏輯在裡面,而且有些邏輯很有可能會影響頁面的展示,所以javascript下載完畢後,瀏覽器就得馬上執行,所以我們就會看到這樣的瀑布圖,如下圖所示:

 

  上面的空白區就是瀏覽器在執行javascript程式碼所要花費的時間。瀏覽器開啟多少個連線是瀏覽器自發的行為,這個自發行為主要出於提升瀏覽器併發下載效率的角度出發的。由於現在瀏覽器的連線基本都是採取的是http1.1協議,也就是使用的長連線,那麼連線建立後這個連線就會長期維護,如果這個長連線是單獨的靜態資源伺服器上的長連線,這個問題倒沒什麼,如果這個長連線放在主域名下面,問題就來了,主域名在頁面初始化載入時候會用來下載html,如果我們為提高併發下載效率,讓這個主域名下還放置其他的靜態資源,那麼可能會導致瀏覽器和主域名的伺服器下維護更多的長連線,而頁面後續操作基本是使用ajax來操作的,而ajax往往只會複用其中一個長連線,那麼其他多餘的長連線等於要空轉了,這個空轉還需要消耗瀏覽器和伺服器的系統資源,所以我們發現主域名下的請求資源型別一定要認真加以控制,能遷移到單獨的靜態資源伺服器上的一定要進行遷移,儘量讓主域名下處理的請求都是包含業務邏輯的請求,這樣就可以有效提升系統資源的使用率。這個問題進一步思考下去,我們就會發現如果服務端的業務應用伺服器之前放置一個反向代理,反向代理都是使用靜態資源伺服器,而靜態資源伺服器對併發的承載能力是遠超業務應用伺服器,如果主域名下我們不小心放置了太多靜態資源,要是後臺使用了反向代理,那麼反向代理也可以減輕這種長連線所造成的計算資源損失。

  上面這些場景都是在瀏覽器同步請求下進行了,那麼換到非同步請求這個並行載入靜態資源的手段還有效嗎?回答這個問題前,我們首先要想想非同步載入會導致新的靜態資源被載入嗎?這個當然可能,特別是在前端MVC的場景下,我們會把模板技術放到瀏覽器端完成,這個時候有些html模板一開始可能會包含在javascript程式碼裡,作為一個變數儲存下來,而這個模板裡很有可能包含好多新的圖片被使用,當ajax從服務端獲取資料後,解析了這個模板,然後我們把構造好的模板加入到頁面的DOM結構裡,瀏覽器重新渲染頁面時候看到很多新圖片需要載入,就有可能會開啟多個連線進行並行載入來提升資源載入效率,如果碰到通過ajax技術動態載入外部CSS檔案,那麼這個並行載入情況就會更加突出了,因為css檔案裡很有可能包含大量的圖片資源,如果我們把不變的靜態資源都放置在了單獨的靜態資源伺服器,那麼這個並行載入就不會在主域名下開啟更多長連線,由此可見,將靜態資源使用單獨的域名的靜態資源伺服器處理的好處非常之多。

  現在http2.0協議還在起草之中,http2.0如果落地將會給web前端優化技術產生重大影響,http2.0打算在一個頁面裡只使用一個tcp/Ip連線,不過http2.0會在這個連線上進行鏈路複用,也就是讓一個連線上也能做到並行操作,讓連線的利用率更高,如果http2.0落地後,web前端裡那些用於減少http連線數的手段都會失去市場了,因為協議本身就能處理好併發的問題了,到時像外部css檔案,外部javascript檔案,css sprite技術說不定就要成為歷史了。

  看來本主題又寫不完了,下篇接著寫吧,今天是元宵節,這裡我祝大家節日快樂。

相關文章