http://blog.jobbole.com/84328/javascript
1、簡介css
網站的web前端要實現高效,第一個要解決的短板就是網絡的延遲性對網站的加載效率的影響,固然不少人會說網速快不快這是網絡運營商的問題,不是網站的問題,可是你們確定也見過就算咱們用上了千兆寬帶也會有些網站加載速度慢的讓人沒法忍受,網站自己的確是無法控制網絡速度的能力,可是若是咱們不下降網絡對頁面加載效率的影響,其餘任何優化網站的手段也就無從談起,緣由就是網絡效率對於網頁加載效率的影響是起到大頭做用的,只有這個大頭被解決了,那麼解決其餘的小頭才能發揮做用。html
大型動態網站之因此能夠作到能快速響應高併發,它們都是儘可能讓本身的網站靜態化,固然這種靜態化毫不是把網站就作成靜態網站,而是在充分理解了靜態網站在提高網站響應速度的基礎上對動態網站進行改良。網站靜態化的關鍵點是動靜分離,解決這個關鍵點的本質就是爲了下降網速對網站加載效率的影響/
靜態網站很是簡單,它就是經過一個url訪問web服務器上的一個網頁,web服務器接收到請求後在網絡上使用http協議將網頁返回給瀏覽器,瀏覽器經過解析http協議最終將頁面展現在瀏覽器裏,有時這個網頁會比較複雜點,裏面包含了一些額外的資源例如:圖片、外部的css文件、外部的js文件以及一些flash之類的多媒體資源,這些資源會單獨使用http協議把信息返回給瀏覽器,瀏覽器從頁面裏的src,href、Object這樣的標籤將這些資源和頁面組合在一塊兒,最終在瀏覽器裏展現頁面。可是無論什麼類型的資源,這些資源若是咱們不是手動的改變它們,那麼咱們每次請求得到結果都是同樣的。這就說明靜態網頁的一個特色:靜態網頁的資源基本是不會發生變化的。所以咱們第一次訪問一個靜態網頁和咱們之後訪問這個靜態網頁都是一個重複的請求,這種網站加載的速度基本都是由網絡傳輸的速度,以及每一個資源請求的大小所決定,既然訪問的資源基本不會發生變化,那麼咱們重複請求這些資源,本身在那裏空等不是很浪費時間嗎?如是乎,瀏覽器出現了緩存技術,咱們開發時候能夠對那些不變的資源在http協議上編寫相應指令,這些指令會讓瀏覽器第一次訪問到靜態資源後緩存起這些靜態資源,用戶第二次訪問這個網頁時候就再也不須要重複請求了,由於請求資源本地緩存,那麼獲取它的效率就變得異常高效。前端
2、CDN技術
因爲靜態網站的請求資源是不會常常發生變化的,那麼這種資源其實很容易被遷移,咱們都知道網絡傳輸的效率是和距離長短有關係的,既然靜態資源很容易被遷移那麼咱們就能夠把靜態資源服務器按地域分佈在多個服務節點上,當用戶請求網站時候根據一個路由算法將請求落地在離用戶最近的節點上,這樣就能夠減小網絡傳輸的距離從而提高訪問的效率,這就是咱們長提的大名鼎鼎的CDN技術,內容分發網絡技術。java
CDN技術應該由三個步驟組成,首先是解析DNS,找到離用戶最近的CDN服務器,接下來CDN要作一下負載均衡,根據負載均衡策略將請求落地到最合適的一個服務器上,若是CDN服務器上就有用戶所須要的靜態資源,那麼這個資源就會直接返回給瀏覽器,若是沒有CDN服務器會請求遠端的服務器,拉取資源再把資源返回給瀏覽器,如此同時拉取的資源也被緩存在CDN服務器上,下次訪問就不須要在請求遠端的服務器了,CDN存儲資源的方式使用的是緩存,這個緩存的載體是和apache,nginx相似的服務器,咱們通常稱之爲http加速器,之因此成爲http加速器是爲了和傳統靜態web服務器區別開來,傳統的靜態資源服務器通常都是從持久化設備上讀取文件,而http加速器則是從內存裏讀取,不過具體存儲的計算模型會根據硬件特色作優化使得資源讀取的效率更高,常見的http加速器有varnish,squid。Ngnix加上緩存模塊也是能夠當作http加速器使用的,無論使用什麼技術CDN的服務器基本都是作一個就近的緩存操做
3、減小http請求nginx
網絡傳輸效率還和咱們傳輸資源的大小有關,所以咱們在資源傳輸前將其壓縮,減少資源的大小從而達到提高傳輸效率的目的;另外,每一個http請求其實都是一個tcp的請求,這些請求在創建鏈接和釋放鏈接都會消耗不少系統資源,這些性能的消耗時常會比傳輸內容自己還要大,所以咱們會盡力減小http請求的個數來達到提高傳輸效率的目的或者使用http長鏈接來消除創建鏈接和釋放鏈接的開銷。
4、動靜分離
我經常認爲最佳的性能優化手段就是使用緩存了,可是緩存的數據通常都是那些不會常常變化的數據,上文裏說到的瀏覽器緩存,CDN其實都是能夠當作緩存手段來理解,它們也是提高網站性能最爲有效的方式之一,可是這些緩存技術到了動態網站卻變得異常很差實施,這究竟是怎麼回事了?
首先動態網站和靜態網站有何不一樣呢?我以爲動態網站和靜態網站的區別就是動態網站網頁雖然也有一個url,可是動態網站網頁的內容根據條件不一樣是會發生改變的,並且這些變化的內容倒是同一個url,url在靜態網站裏就是一個資源的地址,那麼在動態網站裏一個地址指向的資源實際上是不一樣的。由於這種不一樣因此咱們無法把動態的網頁進行有效的緩存,並且不恰當的使用緩存還會引起錯誤,因此在動態網頁裏咱們會在meta設定頁面不會被瀏覽器緩存。
若是每次訪問動態的網頁該網頁的內容都是徹底不一樣的,也許咱們就沒有必要寫網站靜態化的主題了,現實中的動態網頁每每只是其中一部分會發生變化,例如電商網站的菜單、頁面頭部、頁面尾部這些其實都不會常常發生變化,若是咱們只是由於網頁一小部分常常變化讓用戶每次請求都要重複訪問這些重複的資源,這實際上是很是消耗計算資源了,咱們來作個計算吧,假如一個動態頁面這些不變的內容有10k,該網頁一天有1000萬次的訪問量,那麼天天將消耗掉1億kb的網絡資源,這個其實很不划算的,並且這些重複消耗的寬帶資源並無爲網站的用戶體驗帶來好處,相反還拖慢了網頁加載的效率。那麼咱們就得考慮拆分網頁了,把網頁作一個動靜分離,讓靜態的部分當作不變的靜態資源進行處理,動態的內容仍是動態處理,而後在合適的地方將動靜內容合併在一塊兒。
5、動靜合併web
內容動靜分離後,須要把他組合起來,即動靜合併。動靜合併的位置很是的關鍵,這個位置的選擇會直接致使咱們整個web前端的架構設計。ajax
在Java的web開發裏,頁面技術jsp自己就包含了將頁面動靜分離的手段,例以下面的代碼:算法
1
2
3
4
5
6
7
|
<%@ include file=」header.jsp」 %>
<
body
>
……….
<%@ include file=」footer.jsp」 %>
|
通常一個網站的頭部和尾部都是同樣,所以咱們把頭部的代碼單獨放置在一個header.jsp頁面裏,頁面尾部的代碼放置下footer.jsp頁面裏,這樣技術人員在開發頁面時候就再也不須要重複編寫這些重複的代碼,只須要引用便可,這個作法最大的好處就是能夠避免不一樣頁面在相同代碼這塊的不一致性,假如沒有這個統一引用的話,手動編寫或者複製和粘貼,出錯的機率是很是的高的。spring
可是這個作法有一個問題,問題就是這種動靜分離其實都是做用於單個頁面的,也就是說每一個頁面都要手動的重複這個動靜分離的操做,大多數狀況這種作法都不會有什麼問題,可是對於一個大型網站而言這種作法就有可能會製造沒必要要的麻煩,這裏我截取了一張京東的首頁,以下圖所示:
講述前我要事先聲明下,京東網站可能不存在我要講述的問題,我這裏只是使用京東網站的首頁作例子來講明,看圖裏的首頁和食品兩個條目,有些公司作這樣的網站時候這些導航進入的頁面會是一個獨立的工程,每一個工程都是由獨立的項目組開發維護的,雖然項目組不一樣可是他們頁面的總體結構會是一致的,若是按照上面的動靜分離手段,那麼每一個項目組都要獨立維護一份相同的頭部尾部資源,這個時候麻煩來了,若是該公司要新增個新的條目,那麼每一個項目組都要更新本身不變的資源,若是該企業一共分了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
>
<
meta
http-equiv
=
"Content-Type"
content
=
"text/html; charset=utf-8"
/>
<
title
>#springMessage("page_upop_title")</
title
>
<
meta
http-equiv
=
"X-UA-Compatible"
content
=
"requiresActiveX=true"
/>
<
meta
name
=
"keywords"
content
=
'#springMessage("page_upop_keywords")'
/>
<
meta
content
=
'#springMessage("page_upop_description")'
name
=
"description"
/>
</
head
>
<
body
oncontextmenu
=
"return false"
onselectstart
=
"return false"
>
#if($pageHead)
#parse($pageHead)
#end
$screen_content
#parse($page_footer)
</
body
>
</
html
>
|
頁面裏咱們能夠引入這個佈局格式,這個佈局文件其實就是頁面裏不變的東西抽取了出來,它完成了頁面動靜分離,頁面只要應用這個佈局文件便可,到了這裏這個佈局文件和前面的include方式區別不大,那麼咱們再看看下面的代碼:
1
|
<
property
name
=
"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
|
<!--#
include
file=
"info.htm"
-->
|
頁面通常使用註釋的方式引入,這個和jsp的引入有點區別的,SSI的作法其實和服務端的引入相似,只不過使用SSI將原本服務端作的動靜整合交由了apache完成了,咱們能夠把靜態文件直接放置在Apache這裏,若是這個靜態web服務器上升到CDN,那麼這些靜態資源就能夠在靠近用戶的地方使用,SSI說白了就是像apache這樣的靜態資源服務器接收到服務端返回後,將一部份內容插入到頁面了,而後將完整頁面返回至瀏覽器。這個作法若是優化的得當,能夠很好的提高網站的加載效率。
ESI
Apache這樣的靜態資源服務器還支持一種動靜整合的技術,這個技術就是ESi,它的英文全稱叫作Edge Side Includes,它和SSI功能相似,它的用法以下所示:
1
|
<
esi:include
src
=
"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架構)》
6、Web前端優化
關於Web前端優化的請參考《Web前端優化最佳實踐及工具集錦》,《探真無阻塞加載javascript腳本技術》,《【Web優化】Yslow優化法則(彙總篇)》
7、總結
在web開發裏,除了須要瀏覽器處理的,其餘技術均可以當作服務端來理解,若是咱們網站使用到了CDN,使用到了靜態web服務器例如apache,以及服務端的web容器例如jboss,那麼按請求的行進路徑,咱們結果處理越早那麼網站響應效率也就越高,因此當請求在CDN返回了,那麼確定比在apache返回效率高,在apache就返回了確定比jboss返回的效率高,再則服務端的web容器自己由於服務端程序運行要消耗部分系統資源,因此它在處理請求的效率會比CDN和apache差不少,因此當咱們按照動靜分離策略拆分出了靜態資源後,這個資源能不放在最底層的服務端的web容器處理就不要放在服務端的web容器裏處理。