程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 網頁編程 >> PHP編程 >> 關於PHP編程 >> 使用ETags減少Web應用帶寬和負載第1/2頁

使用ETags減少Web應用帶寬和負載第1/2頁

編輯:關於PHP編程

介紹

最近,大眾對於REST風格應用架構表現出強烈興趣,這表明Web的優雅設計開始受到人們的注意。現在,我們逐漸理解了“3W架構(Architecture of the World Wide Web)”內在所蘊含的可伸縮性和彈性,並進一步探索運用其范式的方法。本文中,我們將探究一個可被Web開發者利用的、鮮為人知的工具,不引人注意的“ETag響應頭(ETag Response Header)”,以及如何將它集成進基於Spring和Hibernate的動態Web應用,以提升應用程序性能和可伸縮性。

我們將要使用的Spring框架應用是基於“寵物診所(petclinic)”的。下載文件中包含了關於如何增加必要的配置及源碼的說明,你可以自己嘗試。

什麼是“ETag”?

HTTP協議規格說明定義ETag為“被請求變量的實體值” (參見 http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html —— 章節 14.19)。 另一種說法是,ETag是一個可以與Web資源關聯的記號(token)。典型的Web資源可以一個Web頁,但也可能是JSON或XML文檔。服務器單獨負責判斷記號是什麼及其含義,並在HTTP響應頭中將其傳送到客戶端。

ETag如何幫助提升性能?

聰明的服務器開發者會把ETags和GET請求的“If-None-Match”頭一起使用,這樣可利用客戶端(例如浏覽器)的緩存。因為服務器首先產生ETag,服務器可在稍後使用它來判斷頁面是否已經被修改。本質上,客戶端通過將該記號傳回服務器要求服務器驗證其(客戶端)緩存。

其過程如下:

  1. 客戶端請求一個頁面(A)。
  2. 服務器返回頁面A,並在給A加上一個ETag。
  3. 客戶端展現該頁面,並將頁面連同ETag一起緩存。
  4. 客戶再次請求頁面A,並將上次請求時服務器返回的ETag一起傳遞給服務器。
  5. 服務器檢查該ETag,並判斷出該頁面自上次客戶端請求之後還未被修改,直接返回響應304(未修改——Not Modified)和一個空的響應體。

本文的其余部分將展示在基於Spring框架的Web應用中利用ETag的兩種方法,該應用使用Spring MVC。首先我們將使用Servlet 2.3 Filter,利用展現視圖(rendered view)的MD5校驗和(checksum)以實現生成ETag的方法(一個“淺顯的”ETag實現)。 第二種方法使用更為復雜的方法追蹤view中所使用的model,以確定ETag有效性(一個“深入的”ETag實現)。盡管我們使用的是Spring MVC,但該技術可以應用於任何MVC風格的Web框架。

在我們繼續之前,強調一下這裡所展現的是提升動態產生頁面性能的技術。已有的優化技術也應作為整體優化和應用性能特性調整分析的一部分來考慮。(見下)。

自頂向下的Web緩存

本文主要涉及對動態生成頁面使用HTTP緩存技術。當考慮提升Web應用的性能的時候,應采取一個整體的、自頂向下的方法。為了這一目的,理解HTTP請求經過的各層是很重要的,應用哪些適當的技術取決於你所關注的熱點。例如:

  • 將Apache作為Servlet容器的前端,來處理如圖片和javascript腳本這樣的靜態文件,而且還可以使用FileETag指令創建ETag響應頭。
  • 使用針對javascript文件的優化技術,如將多個文件合並到一個文件中以及壓縮空格。
  • 利用GZip和緩存控制頭(Cache-Control headers)。
  • 為確定你的Spring框架應用的痛處所在,可以考慮使用 JamonPerformanceMonitorInterceptor
  • 確信你充分利用ORM工具的緩存機制,因此對象不需要從數據庫中頻繁的再生。花時間確定如何讓查詢緩存為你工作是值得的。
  • 確保你最小化數據庫中獲取的數據量,尤其是大的列表。如果每個頁面只請求大列表的一個小子集,那麼大列表的數據應由其中某個頁面一次獲得。
  • 使放入到HTTP session中的數據量最小。這樣內存得到釋放,而且當將應用集群的時候也會有所幫助。
  • 使用數據庫明細(database profiling)工具來查看在查詢的時候使用了什麼索引,在更新的時候整個表沒有被上鎖。

當然,應用性能優化的至理名言是:兩次測量,一次剪裁(measure twice, cut once)。哦,等等,這是對木工而言的!沒錯,但是它在這裡也很適用!

ETag Filter內容體

我們要考慮的第一種方法是創建一個Servlet Filter,它將基於頁面(MVC中的“View”)的內容產生其ETag 記號。乍一看,使用這種方法所獲得的任何性能提升看起來都是違反直覺的。我們仍然不得不產生頁面,而且還增加了產生記號的計算時間。然而,這裡的想法是減少帶寬使用。在大的響應時間情形下,如你的主機和客戶端分布在這個星球的兩端,這很大程度上是有益的。我曾見過東京辦公室使用紐約服務器上托管的應用,其響應時間達到了 350 ms。隨著並發用戶數的增長,這將變成巨大的瓶頸。

代碼

我們用來產生記號的技術是基於從頁面內容計算MD5哈希值。這通過在響應之上創建一個包裝器來實現。該包裝器使用字節數組來保存所產生的內容,在filter鏈處理完成之後我們利用數組的MD5哈希值計算記號。

doFilter方法的實現如下所示。
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException,
ServletException {
HttpServletRequest servletRequest = (HttpServletRequest) req;
HttpServletResponse servletResponse = (HttpServletResponse) res;

  ByteArrayOutputStream baos = new ByteArrayOutputStream();
ETagResponseWrapper wrappedResponse = new ETagResponseWrapper(servletResponse, baos);
chain.doFilter(servletRequest, wrappedResponse);

  byte[] bytes = baos.toByteArray();

  String token = '"' + ETagComputeUtils.getMd5Digest(bytes) + '"';
servletResponse.setHeader("ETag", token); // always store the ETag in the header

  String previousToken = servletRequest.getHeader("If-None-Match");
if (previousToken != null && previousToken.equals(token)) { // compare previous token with current one
logger.debug("ETag match: returning 304 Not Modified");
servletResponse.sendError(HttpServletResponse.SC_NOT_MODIFIED);
// use the same date we sent when we created the ETag the first time through
servletResponse.setHeader("Last-Modified", servletRequest.getHeader("If-Modified-Since"));
} else  { // first time through - set last modified time to now
Calendar cal = Calendar.getInstance();
cal.set(Calendar.MILLISECOND, 0);
Date lastModified = cal.getTime();
servletResponse.setDateHeader("Last-Modified", lastModified.getTime());

  logger.debug("Writing body content");
servletResponse.setContentLength(bytes.length);
ServletOutputStream sos = servletResponse.getOutputStream();
sos.write(bytes);
sos.flush();
sos.close();
}
}

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved