- 讀後感
先說說《高性能HTML5》這本書的讀後感吧,個人覺得這本書前兩章跟書的標題完全搭不上關係,或者說只能算是講解了“高性能”這三個字,HTML5完全不見蹤影。個人覺得作者應該首先把HTML5的大菜拿出來講一講,再去分析性能優化的內容,這樣纔會有吸引力。因爲只是在線試讀,沒有機會看後面的內容,所以不胡亂評價了。
雖然我覺得這本書沒說到點子上,但還是從“高性能”方面學到了很多東西------又一次擴大了知識面!以前,我一直認爲一套架構穩定、後臺高質量的代碼就能讓系統高效,但讀完這本書兩章內容之後,發現前端有那麼多可以提高性能的方式,一直被我輕視的前臺技術也能有這麼“花哨”,又一次井底之蛙了- -||。
在看完這兩章內容之後,我意猶未盡,於是乎從網上搜索關鍵字“Java Web高性能”,在IBM社區找到兩篇不錯的文章,而讓人更意外的是我發現那兩篇文章的內容跟《高性能HTML5》前兩章高度相似,不知道是誰抄襲誰的,大家可以鑑別下真僞,下面附上地址。
http://dl2.iteye.com/upload/attachment/0097/9373/b0e69540-e703-3530-81bb-c93de7b850a6.pdf
http://www.ibm.com/developerworks/cn/java/j-lo-javawebhiperf1/
http://www.ibm.com/developerworks/cn/java/j-lo-javawebhiperf2/
前面是讀後感,下面是我針對最近幾天學習到的提高Web性能進行了篇幅不小的總結,一來爲新人提供幫助,二來自己做一下筆記,加深記憶。
- 性能之前端篇
--減少重繪
在HTML頁面完成展現之後,動態改變頁面元素或調整CSS樣式都會引起瀏覽器重繪,性能的損耗直接取決於動態改變的範圍:如果只是改變一個元素的顏色之類的信息則只會重繪該元素;而如果是增刪節點或調整節點位置則會引起其兄弟節點也一併重繪。
減少重繪並不是說不要重繪,而是要注意重繪範圍:①改動的DOM元素越深則影響越小,所以儘量深入節點改動;②對某些DOM樣式有多重變動儘量合併到一起修改。
以改變一個<a>標籤的背景色、寬度和顏色爲例。
- <a href="javascript:void(0);" id="example">傳統的代碼</a>
- <script>
- var example = document.getElementById("example");
- example.ondblclick = function() {
- example.style.backgroundColor = "red";
- example.style.width = "200px";
- example.style.color = "white";
- }
- </script>
- <style>
- .dblClick {
- width: 200px;
- background: red;
- color: white;
- }
- </style>
- <a href="javascript:;" id="example">CSS優化的代碼</a>
- <script>
- var example = document.getElementById("example");
- example.ondblclick = function() {
- example.className = "dblClick";
- }
- </script>
--避免腳本阻塞加載
當瀏覽器在解析常規的script標籤時,它需要等待script下載完畢,再解析執行,而後續的HTML代碼只能等待。CSS文件引入要放在<head>頭部,因爲這是HTML渲染必備元素。爲了避免阻塞加載,應把腳本放到文檔的末尾,而CSS是需要放在頭部的!
- <head>
- <link rel="stylesheet" href="common.css">
- ......
- <script src="example.js"></script>
- </body>
--避免節點深層級嵌套
深層級嵌套的節點在初始化構建時往往需要更多的內存佔用,並且在遍歷節點時也會更慢些,這與瀏覽器構建DOM文檔的機制有關。瀏覽器會把整個HTML文檔的結構存儲爲DOM“樹”結構。當文檔節點的嵌套層次越深,構建的DOM樹層次也會越深。
如下代碼,完全能夠去掉<div>或<span>其中一個標籤。
- <div>
- <span>
- <label>嵌套</label>
- </span>
- </div>
--頁面緩存
通常不設置緩存的情況下,每次刷新頁面都會重新讀取服務器的文件,而如果設置緩存之後,所有文件都可以從本地取得,這樣明顯極大提高了頁面效率。
我們可以通過設置頁面頭的expires來定義頁面過期時間,將過期時間定久一點就達到了“永久”緩存。
- <meta http-equiv="expires" content="Sunday 26 October 2099 01:00 GMT" />
- <script src="example2014-6-17.js"></script>
- //如果是JSP,可以用EL表達式來取時間戳信息,這樣管理更加方便
- <script src="example${your time param}.js"></script>
- //或者這樣的寫法更優秀:
- <script src="example.js?time=2014-6-7"></script>
- <script src="example.js?time=${your time param}"></script>
--壓縮合並文件
所有涉及到請求數據的文件儘量做壓縮,比如Javascript文件、css文件及圖片文件,特別是圖片文件,如果沒有高清晰要求,完全可以壓縮後再使用。
數量少體積大的文件要比數量多體積小的文件加載速度快,所以有時候可以考慮將多個js文件、多個css文件合併在一起。
除此之外減少HTML文檔大小還可以採取下面幾種方法:
①刪掉HTM文檔對執行結果無影響的空格空行和註釋
②避免Table佈局
③使用HTML5
--HTML+CSS3+Javascript各司其職
讓三元素各司其職才能做出高性能的網頁:HTML是頁面之本也是內容之源,有了它就能跟CSS和Javascript交互;CSS3可以說是展現大師,而且日漸強大的CSS能代替Javascript做很多動態的事情如漸變、移動等動態效果;Javascript是動態數據之王,舊瀏覽器依靠js來完成動態效果展現,但現在的CSS也能完成js的工作,所以儘量將工作交給css,這樣會獲得更好的性能。(這個說得有點大)
--圖像合併實現CSS Sprites
圖像合併其實就是把網頁中一些背景圖片整合到一張圖片文件中,再利用CSS 的“background-image”,“background- repeat”,“background-position”的組合進行背景定位,background-position可以用數字能精確的定位出背景圖片的位置。
一個頁面要用到多個圖標,完全可以將多個圖標合併成一個圖,然後只需要發一次圖片請求,通過css定位分割圖標即可。
--避免使用Iframe
使用iframe並不會增加同域名下的並行下載數,瀏覽器對同域名的連接總是共享瀏覽器級別的連接池,在頁面加載過程中iframe元素還會阻塞父文檔onload事件的觸發。並且iframe是html標籤中最消耗資源的標籤,它的開銷比DIV、SCRIPT、STYLE等DOM高1~2個數量級。
避免onload事件被阻塞,可使用JavaScript動態的加載iframe元素或動態設置iframe的src屬性(但其僅在高級瀏覽器IE9及以上有效)。
- <iframe id="if"></iframe>
- document.getElementById("if").setAttribute("src","url");
--多域名請求
一般來說,瀏覽器對於相同域名的圖片,最多用2-4個線程並行下載(不同瀏覽器的併發下載數是不同的)。而相同域名的其他圖片,則要等到其他圖片下載完後纔會開始下載。
有時候,圖片數據太多,一些公司的解決方法是將圖片數據分到多個域名的服務器上,這在一方面是將服務器的請求壓力分到多個硬件服務器上,另一方面,是利用了瀏覽器的特性。(大家可以去新浪、騰訊門戶網站查看,這些大型站點同一頁面加載的圖片可能由多個站點提供)
注:一個HTML請求的域名也不要太多(2~3個差不多),多了可能造成不同服務器連接時間差異,反而影響速度。
--避免空鏈接屬性
如<img src=""><a href="">這樣的設置方式是非常不可取的,即使鏈接爲空,在舊的瀏覽器也會以固定步驟發送請求信息。
另外<a href="#"></a>也不可取,最好的方式是在鏈接中加一個空的js代碼<a href="javascript:void();"></a>
--使用圖像的BASE64編碼
base64是一串字符串,他可以代表一個圖片的所有信息,也就是可以通過<img src="data:image/png;base64,S">(S表示一串base64碼)來顯示圖片,這種方式不需要再向服務器發送請求,完全由瀏覽器解析成圖片。
目前高級瀏覽器都支持此功能,但要注意兩點:①低版本瀏覽器(如IE7)不支持;②base64字符串長度隨圖片的大小及複雜度成正比,base64也像URL一樣,也有超出長度的情況(在IE8下很常見)。所以要根據情況來使用。
--顯式設置圖片的寬高
如果HTML裏的圖片沒有指定尺寸(寬和高),或者代碼描述的尺寸與實際圖片的尺寸不符時,瀏覽器則要在圖片下載完成後再“回溯”該圖片並重新顯示,這會消耗額外時間。
<img src="demo.jpg" width="200" height="200">
--顯式指定文檔字符集
如果瀏覽器不能獲知頁面的編碼字符集,一般都會在執行腳本和渲染頁面前,把字節流緩存,然後再搜索可進行解析的字符集,或以默認的字符集來解析頁面代碼,這會導致消耗不必要的時間。
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
--漸進式增強設計
漸進式增強設計的通俗解釋就是:首先寫一段滿足所有瀏覽器的基本樣式,再在後面針對不同高級瀏覽器編寫更漂亮的樣式
如下代碼,所有瀏覽器都支持background-color: #2067f5;滿足了瀏覽器基本現實需求,而後面的background-image: -webkit-gradient等則爲不同高級瀏覽器使用,只要瀏覽器識別就能執行這段代碼(不識別,CSS也不會報錯只會直接忽略)。
- <div class="someClass"></div>
- .someClass
- { width: 100px;
- height: 100px;
- background-color: #2067f5;
- background-image: -webkit-gradient(linear, left top, left bottom, from(#2067f5),
- to(#154096));
- background-image: -webkit-linear-gradient(top, #2067f5, #154096);
- background-image: -moz-linear-gradient(top, #2067f5, #154096);
- background-image: -ms-linear-gradient(top, #2067f5, #154096);
- background-image: -o-linear-gradient(top, #2067f5, #154096);
- background-image: linear-gradient(to bottom, #2067f5, #154096);
- }
--懶加載與預加載
預加載和懶加載,是一種改善用戶體驗的策略,它實際上並不能提高程序性能,但是卻可以明顯改善用戶體驗或減輕服務器壓力。
預加載表示當前用戶在請求到需要的數據之後,頁面自動預加載下一次用戶可能要看的數據,這樣用戶下一次操作的時候就立刻呈現,依次類推。
懶加載表示用戶請求什麼再顯示什麼,如果一個請求要響應的時間非常長,就不推薦懶加載。
--Flush機制
當一個頁面非常大,內容非常多,可以採用flush的形式分部分返回給頁面,這樣能告訴用戶我正在工作,顯示一部分內容比白屏等很長時間要好得多。在Java Web技術中,實現Flush非常簡單,只要調用 HttpServletResponse.getWriter輸出流的flush方法,就可以將已經完成加載的內容寫回給客戶端。
這種方式只適用於返回數據特別多、請求時間特別長的情況,常規數據還是用正常的實時全部返回最佳。這種實現方式實際會增加瀏覽器渲染時間和用戶整體等待時間,但從用戶體驗上會更加優秀。
- 性能之服務器優化
--CDN機制
所謂的CDN,就是一種內容分發網絡,它採用智能路由和流量管理技術,及時發現能夠給訪問者提供最快響應的加速節點,並將訪問者的請求導向到該加速節點,由該加速節點提供內容服務。
通俗點說,你在成都(瀏覽器)購買了北京賣家(服務器)的產品,北京賣家通過快遞(CDN服務)寄送包裹,從北京到成都可以走路、坐汽車、火車或飛機,而採用CND的快遞會選擇飛機直達,因爲這種寄送方式最快。
當然使用CDN有兩個注意事項:
1、CDN加速服務很貴,如果你覺得你的網站值得加速,可以選擇購買;
2、CDN不適合局域性網站,比如你的網站只有某一個片區訪問或者局域網訪問,因爲區域性網絡本來就很近,無需CDN加速。
--HTTP協議的合理使用
瀏覽器緩存帶來的性能提升已經衆人皆知了,而很多人卻並不知道瀏覽器的緩存過期時間、緩存刪除、什麼頁面可以緩存等,都可以由我們程序員來控制,只要您熟悉HTTP協議,就可以輕鬆的控制瀏覽器。
擴展閱讀:深入理解HTTP協議
--動靜分離
所謂的動靜分離,就是將Web應用程序中靜態和動態的內容分別放在不同的Web服務器上,有針對性的處理動態和靜態內容,從而達到性能的提升。我們知道如果一個HTML有多個域名請求數據文件會提高
Tomcat服務器在處理靜態和併發問題上比較弱,所以事先動靜分離的方式一般會用Apache+Tomcat、Nginx+Tomcat等。
以Apache+Tomcat爲例,其運行機理是:頁面請求首先給Apache,然後Apache分析請求信息是靜態還是動態,靜態則本機處理,動態則交給Tomcat做處理。
這其實是負載均衡的雛形,這樣的實現不用讓開發人員做任何特殊開發,一個<img src="demo.jpg">交給服務器即可,至於這個文件是從Apache還是從Tomcat取得,開發人員完全無需關注。
--HTTP持久連接
持久連接(Keep-Alive)也叫做長連接,它是一種TCP的連接方式,連接會被瀏覽器和服務器所緩存,在下次連接同一服務器時,緩存的連接被重新使用。HTTP無狀態性表示了它不屬於長連接,但HTTP/1.1提供了對長連接的支持(不過這必須依賴瀏覽器和服務器雙方均支持長連接功能才行),最常見的HTTP長連接例子是“斷點下載”。
瀏覽器在請求的頭部添加 Connection:Keep-Alive,以此告訴服務器“我支持長連接,你支持的話就和我建立長連接吧”,而倘若服務器的確支持長連接,那麼就在響應頭部添加“Connection:Keep-Alive”,從而告訴瀏覽器“我的確也支持,那我們建立長連接吧”。服務器還可以通過Keep-Alive:timeout=..., max=...的頭部告訴瀏覽器長連接失效時間。
配置長連接通常是要服務器支持設置,有測試數據顯示,使用長連接和不使用長連接的性能對比,對於Tomcat配置的maxKeepAliveRequests爲50來說,效率竟然提升了將近5倍。
--GZIP壓縮技術
HTTP協議支持GZIP的壓縮格式,當服務器返回的HTML信息報頭中包含Content-Encoding:gzip,它就告訴瀏覽器,這個響應的返回數據已經壓縮成GZIP格式,瀏覽器獲得數據後要進行解壓縮操作,一定程度上減輕了服務器傳輸數據的壓力。
很多服務器已經支持通過配置來自動將HTML信息壓縮成GZIP,比如tomcat、又比如很火的Nginx。如果無法配置服務器級別的GZIP壓縮機制,可以改爲程序壓縮。
- // 監視對 gzipCategory 文件夾的請求
- @WebFilter(urlPatterns = { "/gzipCategory/*" })
- public class GZIPFilter implements Filter {
- @Override
- public void doFilter(ServletRequest request, ServletResponse response,
- FilterChain chain) throws IOException, ServletException {
- String parameter = request.getParameter("gzip");
- // 判斷是否包含了 Accept-Encoding 請求頭部
- HttpServletRequest s = (HttpServletRequest)request;
- String header = s.getHeader("Accept-Encoding");
- //"1".equals(parameter) 只是爲了控制,如果傳入 gzip=1,才執行壓縮,目的是測試用
- if ("1".equals(parameter) && header != null && header.toLowerCase().contains("gzip")) {
- HttpServletResponse resp = (HttpServletResponse) response;
- final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
- HttpServletResponseWrapper hsrw = new HttpServletResponseWrapper(
- resp) {
- @Override
- public PrintWriter getWriter() throws IOException {
- return new PrintWriter(new OutputStreamWriter(buffer,
- getCharacterEncoding()));
- }
- @Override
- public ServletOutputStream getOutputStream() throws IOException {
- return new ServletOutputStream() {
- @Override
- public void write(int b) throws IOException {
- buffer.write(b);
- }
- };
- }
- };
- chain.doFilter(request, hsrw);
- byte[] gzipData = gzip(buffer.toByteArray());
- resp.addHeader("Content-Encoding", "gzip");
- resp.setContentLength(gzipData.length);
- ServletOutputStream output = response.getOutputStream();
- output.write(gzipData);
- output.flush();
- } else {
- chain.doFilter(request, response);
- }
- }
- // 用 GZIP 壓縮字節數組
- private byte[] gzip(byte[] data) {
- ByteArrayOutputStream byteOutput = new ByteArrayOutputStream(10240);
- GZIPOutputStream output = null;
- try {
- output = new GZIPOutputStream(byteOutput);
- output.write(data);
- } catch (IOException e) {
- } finally {
- try {
- output.close();
- } catch (IOException e) {
- }
- }
- return byteOutput.toByteArray();
- }
- ……
- }
- 總結
細節決定成敗,系統慢是由一個又一個不良的小細節造成的,所以開發初期做好充足的準備,開發中認真負責、不偷工減料,維護期更要注重代碼質量,這樣才能讓我們的系統穩定高效。
個人覺得一個產品的新版本質量可以從核心js文件看出來:如果核心js文件大小比上一版本大太多,明顯在性能上就會有問題,我們要爭做像谷歌、亞馬遜這樣優秀的團隊--能夠在功能升級的同時減小核心js文件大小。
轉載自:http://blessht.iteye.com/blog/2081870