網站靜態化思想

b9x_發表於2018-04-20

原文:http://blog.jobbole.com/84328/

一、簡介


  網站的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容器裡處理。

相關文章