提高頁面加載速度的神器--頁面靜態化技術

一、簡介


  網站的web前端要實現高效,第一個要解決的短板就是網絡的延遲性對網站的加載效率的影響,當然很多人會說網速快不快這是網絡運營商的問題,不是網站的問題,但是大家肯定也見過就算我們用上了千兆寬帶也會有些網站加載速度慢的讓人無法忍受,網站本身的確是沒法控制網絡速度的能力,但是如果我們不降低網絡對頁面加載效率的影響,其他任何優化網站的手段也就無從談起,原因就是網絡效率對於網頁加載效率的影響是起到大頭作用的,只有這個大頭被解決了,那麼解決其他的小頭才能發揮作用。

  大型動態網站之所以可以做到能快速響應高併發,它們都是儘量讓自己的網站靜態化,當然這種靜態化絕不是把網站就做成靜態網站,而是在充分理解了靜態網站在提升網站響應速度的基礎上對動態網站進行改良。網站靜態化的關鍵點是動靜分離,解決這個關鍵點的本質就是爲了降低網速對網站加載效率的影響/

  靜態網站非常簡單,它就是通過一個url訪問web服務器上的一個網頁,web服務器接收到請求後在網絡上使用http協議將網頁返回給瀏覽器,瀏覽器通過解析http協議最終將頁面展示在瀏覽器裏,有時這個網頁會比較複雜點,裏面包含了一些額外的資源例如:圖片、外部的css文件、外部的js文件以及一些flash之類的多媒體資源,這些資源會單獨使用http協議把信息返回給瀏覽器,瀏覽器從頁面裏的src,href、Object這樣的標籤將這些資源和頁面組合在一起,最終在瀏覽器裏展示頁面。但是不管什麼類型的資源,這些資源如果我們不是手動的改變它們,那麼我們每次請求獲得結果都是一樣的。這就說明靜態網頁的一個特點:靜態網頁的資源基本是不會發生變化的。因此我們第一次訪問一個靜態網頁和我們以後訪問這個靜態網頁都是一個重複的請求,這種網站加載的速度基本都是由網絡傳輸的速度,以及每個資源請求的大小所決定,既然訪問的資源基本不會發生變化,那麼我們重複請求這些資源,自己在那裏空等不是很浪費時間嗎?如是乎,瀏覽器出現了緩存技術,我們開發時候可以對那些不變的資源在http協議上編寫相應指令,這些指令會讓瀏覽器第一次訪問到靜態資源後緩存起這些靜態資源,用戶第二次訪問這個網頁時候就不再需要重複請求了,因爲請求資源本地緩存,那麼獲取它的效率就變得異常高效。

二、CDN技術

由於靜態網站的請求資源是不會經常發生變化的,那麼這種資源其實很容易被遷移,我們都知道網絡傳輸的效率是和距離長短有關係的,既然靜態資源很容易被遷移那麼我們就可以把靜態資源服務器按地域分佈在多個服務節點上,當用戶請求網站時候根據一個路由算法將請求落地在離用戶最近的節點上,這樣就可以減少網絡傳輸的距離從而提升訪問的效率,這就是我們長提的大名鼎鼎的CDN技術,內容分發網絡技術。

  CDN技術應該由三個步驟組成,首先是解析DNS,找到離用戶最近的CDN服務器,接下來CDN要做一下負載均衡,根據負載均衡策略將請求落地到最合適的一個服務器上,如果CDN服務器上就有用戶所需要的靜態資源,那麼這個資源就會直接返回給瀏覽器,如果沒有CDN服務器會請求遠端的服務器,拉取資源再把資源返回給瀏覽器,如此同時拉取的資源也被緩存在CDN服務器上,下次訪問就不需要在請求遠端的服務器了,CDN存儲資源的方式使用的是緩存,這個緩存的載體是和apache,nginx類似的服務器,我們一般稱之爲http加速器,之所以成爲http加速器是爲了和傳統靜態web服務器區別開來,傳統的靜態資源服務器一般都是從持久化設備上讀取文件,而http加速器則是從內存裏讀取,不過具體存儲的計算模型會根據硬件特點做優化使得資源讀取的效率更高,常見的http加速器有varnish,squid。Ngnix加上緩存模塊也是可以當做http加速器使用的,不管使用什麼技術CDN的服務器基本都是做一個就近的緩存操作

三、減少http請求

網絡傳輸效率還和我們傳輸資源的大小有關,因此我們在資源傳輸前將其壓縮,減小資源的大小從而達到提升傳輸效率的目的;另外,每個http請求其實都是一個tcp的請求,這些請求在建立連接和釋放連接都會消耗很多系統資源,這些性能的消耗時常會比傳輸內容本身還要大,因此我們會盡力減少http請求的個數來達到提升傳輸效率的目的或者使用http長連接來消除建立連接和釋放連接的開銷。

四、動靜分離

  我常常認爲最佳的性能優化手段就是使用緩存了,但是緩存的數據一般都是那些不會經常變化的數據,上文裏說到的瀏覽器緩存,CDN其實都是可以當做緩存手段來理解,它們也是提升網站性能最爲有效的方式之一,但是這些緩存技術到了動態網站卻變得異常不好實施,這到底是怎麼回事了?

  首先動態網站和靜態網站有何不同呢?我覺得動態網站和靜態網站的區別就是動態網站網頁雖然也有一個url,但是動態網站網頁的內容根據條件不同是會發生改變的,而且這些變化的內容卻是同一個url,url在靜態網站裏就是一個資源的地址,那麼在動態網站裏一個地址指向的資源其實是不同的。因爲這種不同所以我們沒法把動態的網頁進行有效的緩存,而且不恰當的使用緩存還會引發錯誤,所以在動態網頁裏我們會在meta設定頁面不會被瀏覽器緩存。
   如果每次訪問動態的網頁該網頁的內容都是完全不同的,也許我們就沒有必要寫網站靜態化的主題了,現實中的動態網頁往往只是其中一部分會發生變化,例如電商網站的菜單、頁面頭部、頁面尾部這些其實都不會經常發生變化,如果我們只是因爲網頁一小部分經常變化讓用戶每次請求都要重複訪問這些重複的資源,這其實是非常消耗計算資源了,我們來做個計算吧,假如一個動態頁面這些不變的內容有10k,該網頁一天有1000萬次的訪問量,那麼每天將消耗掉1億kb的網絡資源,這個其實很不划算的,而且這些重複消耗的寬帶資源並沒有爲網站的用戶體驗帶來好處,相反還拖慢了網頁加載的效率。那麼我們就得考慮拆分網頁了,把網頁做一個動靜分離,讓靜態的部分當做不變的靜態資源進行處理,動態的內容還是動態處理,然後在合適的地方將動靜內容合併在一起。


五、動靜合併

內容動靜分離後,需要把他組合起來,即動靜合併。動靜合併的位置非常的關鍵,這個位置的選擇會直接導致我們整個web前端的架構設計。

 

Java的web開發裏,頁面技術jsp本身就包含了將頁面動靜分離的手段,例如下面的代碼:

1

2

3

4

5

6

7

<%@ include file=”header.jsp” %>

 

<body>

 

         ……….

 

<%@ include file=”footer.jsp” %>

一般一個網站的頭部和尾部都是一樣,因此我們把頭部的代碼單獨放置在一個header.jsp頁面裏,頁面尾部的代碼放置下footer.jsp頁面裏,這樣技術人員在開發頁面時候就不再需要重複編寫這些重複的代碼,只需要引用即可,這個做法最大的好處就是可以避免不同頁面在相同代碼這塊的不一致性,假如沒有這個統一引用的話,手動編寫或者複製和粘貼,出錯的概率是非常的高的。

但是這個做法有一個問題,問題就是這種動靜分離其實都是作用於單個頁面的,也就是說每個頁面都要手動的重複這個動靜分離的操作,大多數情況這種做法都不會有什麼問題,但是對於一個大型網站而言這種做法就有可能會製造不必要的麻煩,這裏我截取了一張京東的首頁,如下圖所示:

講述前我要事先聲明下,京東網站可能不存在我要講述的問題,我這裏只是使用京東網站的首頁做例子來說明,看圖裏的首頁和食品兩個條目,有些公司做這樣的網站時候這些導航進入的頁面會是一個獨立的工程,每個工程都是由獨立的項目組開發維護的,雖然項目組不同但是他們頁面的整體結構會是一致的,如果按照上面的動靜分離手段,那麼每個項目組都要獨立維護一份相同的頭部尾部資源,這個時候麻煩來了,如果該公司要新增個新的條目,那麼每個項目組都要更新自己不變的資源,如果該企業一共分了5個項目組,現在又做了一個新的條目,那麼其他與之無關的項目組都得折騰一次更改統一引用文件的工作,要是做的不仔細就有可能出現頁面展示不一致的問題,爲了解決這個問題,java的web開發裏就會考慮使用模板語言替代jsp頁面技術,例如模板語言velocity,這些模板語言都包含一個佈局的功能,例如velocity就有這樣的功能,我們看看velocity的佈局模板實例,如下所示:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

<html>

 

<head>

 

    <metahttp-equiv="Content-Type"content="text/html; charset=utf-8"/>

 

    <title>#springMessage("page_upop_title")</title>

 

    <metahttp-equiv="X-UA-Compatible"content="requiresActiveX=true"/>

 

    <metaname="keywords"content='#springMessage("page_upop_keywords")'/>

 

    <metacontent='#springMessage("page_upop_description")'name="description"/>

 

</head>

 

<bodyoncontextmenu="return false"  onselectstart="return false">

 

    #if($pageHead)

 

        #parse($pageHead)

 

    #end

 

    $screen_content

 

    #parse($page_footer)

 

</body>

 

</html>

頁面裏我們可以引入這個佈局格式,這個佈局文件其實就是頁面裏不變的東西抽取了出來,它完成了頁面動靜分離,頁面只要應用這個佈局文件即可,到了這裏這個佈局文件和前面的include方式區別不大,那麼我們再看看下面的代碼:

1

<propertyname="layoutUrl"value="layout/default.vm"/><!--指定layout文件-->

這是佈局文件的引用方式,我們可以把佈局文件放置在網絡上,項目裏應用這個文件所在地址即可,這樣我們就把項目裏不變的靜態資源抽取在同一個地方,如果在碰到佈局要做修改,那麼我們只需要改一個地方即可。

不管服務端採取何種動靜分離,動靜資源的整合都是有服務端完成,按照上文提到網站靜態化的思想,這些做法不會給網站性能提升帶來任何好處,它們只是給開發,運維提供了便利而已,我們要把動靜分離往前移,服務端往前移碰到的第一個點就是靜態的web服務器例如apache或ngnix。

(1)web服務器的動靜合併

java的web開發裏我們一般使用jsp來編寫頁面,當然也可以使用先進點的模板引擎開發頁面例如velocity,freemark等,不管我們頁面使用的是jsp還是模板引擎,這些類似html的文件其實並不是真正的html,例如jsp本質其實是個servlet也就是一個java程序,所以它們的本質是服務端語言和html的一個整合技術,在實際運行中web容器會根據服務端的返回數據將jsp或模板引擎解析成瀏覽器能解析的html,然後傳輸這個html到瀏覽器進行解析。由此可見服務端語言提供的開發頁面的技術其實是動靜無法分離的源頭,但是這些技術可以很好的完成動靜資源中的動的內容,因此我們想做動靜分離那麼首先就要把靜的資源從jsp或者模板語言裏抽取出來,抽取出來的靜態資源當然就要交給靜態的web服務器來處理,我們常用的靜態資源服務器一般是apache或ngnix,所以這些靜態資源應該放置在這樣的服務器上,那麼我們是否可以在這些靜態web服務器上做動靜結合呢?答案是還真行,例如apache服務器有個模塊就可以將它自身存儲的靜態資源和服務端傳輸的資源整合在一起,這種技術叫做ESI、SSI,這個時候我們可以把不變的靜態內容製作成模板放置在靜態服務器上,動態內容達到靜態資源服務器時候,使用ESI或者SSI的標籤,把動靜內容結合在一起,這就完成了一個動靜結合操作。

 

爲什麼我們要在服務端前面加個靜態web服務器的道理。我個人覺得在每個服務端之前都佈置一個靜態web服務器,該服務器起到一個反向代理的作用,而且我覺得不管我們是否使用CDN,最好都這麼做,這麼做有如下好處:

好處一:方便日誌的記錄。

好處二:在服務端之前設立了一個安全屏障,即靜態web服務器可以在必要時候過濾有害的請求。

好處三:可以控制流入到服務端的請求個數,當併發很高時候,可以利用靜態web服務器能承擔更高併發的能力來緩衝服務端的壓力,這裏我補充一些實踐技巧,以java裏常用的web容器tomcat爲例,一般官方給出它的最大併發數應該不會超過200,如果我們在tomcat前放置了一個apache服務器,那麼我們可以把tomcat的最大併發數設置爲無效大,把併發數的控制放置在apache這邊控制,這麼做會給我們系統運維帶來很大的好處,tomcat雖然有一個建議最大併發數,但是實際運行裏java的web容器到底能承受多大併發其實要看具體場景了,因此我們如果可以動態控制apache的併發數,這個操作很方便的,那麼我們就可以動態的調整tomcat這樣容器的承載能力。

好處四:可以便於我們做動靜分離。

SSI

這裏我們以apache爲例子講解將動靜分離前移到apache的一些做法,apache有一個功能叫做SSI,英文全稱是Server Side Include,頁面上我們一般這樣使用SSI,SSI有一種標籤,例如:

1

<!--#includefile="info.htm"-->

頁面一般使用註釋的方式引入,這個和jsp的引入有點區別的,SSI的做法其實和服務端的引入類似,只不過使用SSI將本來服務端做的動靜整合交由了apache完成了,我們可以把靜態文件直接放置在Apache這裏,如果這個靜態web服務器上升到CDN,那麼這些靜態資源就可以在靠近用戶的地方使用,SSI說白了就是像apache這樣的靜態資源服務器接收到服務端返回後,將一部分內容插入到頁面了,然後將完整頁面返回至瀏覽器。這個做法如果優化的得當,可以很好的提升網站的加載效率。

ESI

Apache這樣的靜態資源服務器還支持一種動靜整合的技術,這個技術就是ESi,它的英文全稱叫做Edge Side Includes,它和SSI功能類似,它的用法如下所示:

1

<esi:includesrc="test.vm.esi?id=100"max-age="45"/>

它和SSI區別,使用esi標籤獲取的資源來自於緩存服務器,它和SSI相比有明顯的性能優勢,其實網頁特別是一個複雜的網頁我們做了動靜分離後靜態的資源本身還可以拆分,有的部分緩存的時間會長點,有點會短點,其實網頁裏某些動態內容本身在一定時間裏有些資源也是不會發生變化的,那麼這些內容我們可以將其存入到緩存服務器上,這些緩存服務器可以根據頁esi傳來的命令將各個不同的緩存內容整合在一起,由此我們可以發現使用esi我們會享受如下優點:

優點一:靜態資源會存放在緩存裏,那麼獲取靜態資源的效率會更高。

優點二:根據靜態資源的時效性,我們可以對不同的靜態資源設置不同的緩存策略,這就增加了動靜分離方案的靈活性。

優點三:緩存的文件的合併交由緩存服務器完成,這樣就減少了web服務器本身抓取文件的開銷,從而達到提升web服務器的併發處理能力,從而達到提升網站訪問效率的目的。

關於ESI的更多內容請參考《大型網站之網站靜態化(ESI)

(2)CDN的動靜合併

CDN其實也是一組靜態的web服務器,那麼我們是否可以把這些事情放到CDN做了?理論上是可以做到,但是現實卻是不太好做,因爲除了一些超有錢的互聯網公司,大部分公司使用的CDN都是第三方提供的,第三方的CDN往往是一個通用方案,再加上人家畢竟不是自己人,而且CDN的主要目的也不是爲了做動靜分離,因此大部分情況下在CDN上完成這類操作並不是那麼順利,因此我們常常會在服務端的web容器前加上一個靜態web服務器,這個靜態服務器起到一個反向代理的作用,它可以做很多事情,其中一件事情就是可以完成這個動靜結合的問題。

(3)ajax的動靜合併
SSI和ESI是靜態web服務器處理動靜資源整合的手段,那麼我們把這個動靜結合點再往前推,推到瀏覽器,瀏覽器能做到這件事情嗎?如果瀏覽器可以,那麼靜態資源也就可以緩存在客戶端了,這比緩存在CDN效率還要高,其實瀏覽器還真的可以做到這點,特別是ajax技術出現後,瀏覽器來整合這個動靜資源也就變得更加容易了。瀏覽器端的動靜整合的技術稱之爲CSI,英文全稱叫做Client Side Includes,這個技術就是時下javascriptMVC、MVVM以及MVP技術採取的手段,實現CSI一般是採用異步請求的方式進行,在ajax技術還沒出現的年代我們一般採取iframe的方式,不過使用CSI技術頁面加載就會被人爲分成兩次,一次是加載靜態資源,等靜態資源加載完畢,啓動異步請求加載動態資源,這麼一做的確會發生有朋友提到的一種加載延遲的問題,這個延遲我們可以使用適當的策略來解決的.

  ajax是CSI的一種技術手段。不過一般而言,我們使用ajax做動靜分離都是都是從服務端請求一個html片段,到了瀏覽器後,使用dom技術將這個片段整合到頁面裏.

(4)javascriptMVC架構

雖然在瀏覽器使用ajax來進行動靜分離和合並已經比全頁面返回高效很多,但是他還是有問題的,服務端處理完請求最終返回結果其實都是很純粹的數據,可是這些數據我們不得不轉化爲頁面片段返回給瀏覽器,這本質是爲純粹的數據上加入了很多與服務端無用的結構,之所以說無用是因爲瀏覽器自身也可以完成這些結構,爲什麼我們一定要讓服務端做這個事情了?如是乎JavaScript的模板技術出現了,這些模板技術和jsp,velocity類似,只不過它們是通過javascript設計的模板語言,有了javascript模板語言,服務端可以完全不用考慮對頁面的處理,它只需要將有效的數據返回到頁面就行了,使用了javascript模板技術,可以讓我們動靜資源分離做的更加徹底,基本上所有的瀏覽器相關的東西都被靜態化了,服務端只需要把最原始的數據傳輸到瀏覽器即可。講到這裏我們就說到了web前端最前沿的技術了:javascriptMVC架構了。關於此的更多內容請參考《大型網站之網站靜態化(javascriptMVC架構)》

六、Web前端優化

關於Web前端優化的請參考《Web前端優化最佳實踐及工具集錦》,《探真無阻塞加載javascript腳本技術》,《【Web優化】Yslow優化法則(彙總篇)

七、總結

  在web開發裏,除了需要瀏覽器處理的,其他技術都可以當做服務端來理解,如果我們網站使用到了CDN,使用到了靜態web服務器例如apache,以及服務端的web容器例如jboss,那麼按請求的行進路徑,我們結果處理越早那麼網站響應效率也就越高,所以當請求在CDN返回了,那麼肯定比在apache返回效率高,在apache就返回了肯定比jboss返回的效率高,再則服務端的web容器本身因爲服務端程序運行要消耗部分系統資源,所以它在處理請求的效率會比CDN和apache差很多,所以當我們按照動靜分離策略拆分出了靜態資源後,這個資源能不放在最底層的服務端的web容器處理就不要放在服務端的web容器裏處理。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章